Vectors (pgvector)
Store embeddings in Postgres with pgvector and run similarity search via RPC — the same model as Supabase Vector.
Bring your own embeddings
1536).1. Enable pgvector
The vector extension is enabled automatically on new projects. Confirm under dashboard Extensions, or run:
create extension if not exists vector;2. Vector columns
In the table editor pick type vector and set dimensions, or SQL:
create table documents (
id uuid primary key default gen_random_uuid(),
content text not null,
embedding vector(1536),
user_id uuid,
created_at timestamptz not null default now()
);3. Insert embeddings
Pass a number[] from the SDK — Voltbase casts it to vector:
import { createClient } from 'voltbase-js';
const voltbase = createClient(projectUrl, anonKey);
// embedding = await openai.embeddings.create(...).data[0].embedding
const embedding: number[] = /* length 1536 */;
const { data, error } = await voltbase.from('documents').insert({
content: 'How do I reset my password?',
embedding,
});4. HNSW index
From the table Indexes tab choose method hnsw and ops vector_cosine_ops, or:
create index documents_embedding_hnsw
on documents
using hnsw (embedding vector_cosine_ops);5. match_* + rpc()
Like Supabase JS, similarity operators are exposed through a SQL function and rpc():
create or replace function match_documents(
query_embedding vector(1536),
match_threshold float default 0.7,
match_count int default 10
)
returns table (
id uuid,
content text,
similarity float
)
language sql
stable
as $$
select
documents.id,
documents.content,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where 1 - (documents.embedding <=> query_embedding) > match_threshold
order by documents.embedding <=> query_embedding
limit match_count;
$$;const { data, error } = await voltbase.rpc('match_documents', {
query_embedding: queryVec,
match_threshold: 0.78,
match_count: 10,
});6. RLS & grants
Keep functions as SECURITY INVOKER (default) so RLS applies. Grant execute to project roles (replace proj_xxxx with your schema from the dashboard):
grant execute on function match_documents(vector, float, int)
to "proj_xxxx_anon", "proj_xxxx_authenticated";EXECUTE via default privileges for anon/authenticated when created by the connection role — still verify with a test rpc() call using the anon key.7. Metadata filters
Extend the function with extra params (Supabase pattern):
create or replace function match_documents(
query_embedding vector(1536),
match_threshold float default 0.7,
match_count int default 10,
filter_user_id uuid default null
)
returns table (id uuid, content text, similarity float)
language sql stable
as $$
select
documents.id,
documents.content,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where
(filter_user_id is null or documents.user_id = filter_user_id)
and 1 - (documents.embedding <=> query_embedding) > match_threshold
order by documents.embedding <=> query_embedding
limit match_count;
$$;Next: Semantic search example or Row Level Security.