Skip to main content
Powabase projects ship with pgvector preloaded. The vector extension is created at project provision time and used internally by ai.embeddings. You can use it for your own tables too: store embeddings in public.* and run vector similarity queries via PostgREST or direct SQL. For most users, the typed Sources and Knowledge Bases surface is the right path. It manages chunking, embedding, indexing, RLS, billing, and retrieval reranking for you. Rolling your own pgvector tables is for cases where the typed surface doesn’t fit: custom embedding models, specific schema constraints, integration with non-Powabase pipelines. This page covers the user-managed approach so you can choose the right tool for the job.

When to use which

You want…Use
RAG over uploaded documentsSources + KB (/api/sources, /api/knowledge-bases)
Hybrid search (vector + BM25 + rerank)KB search (/api/knowledge-bases/{id}/search)
Custom embedding model / non-text data (image embeddings, audio)User-managed pgvector
Schema-coupled embeddings (“each row has its own embedding column”)User-managed pgvector
Integration with an external indexing pipelineUser-managed pgvector
Per-user RLS that the typed KB surface doesn’t yet supportUser-managed pgvector
The typed surface charges credits per indexing operation and per search; user-managed pgvector only charges credits for vector_search calls (see Billing model). For high-volume retrieval against a fixed corpus, user-managed can be cheaper, at the cost of more setup.

The basic shape

A user-managed embeddings table is just a regular table with a vector(N) column where N is your embedding dimension.
The dimension (1536 above) must match what your embedding model produces. Common choices:
  • OpenAI text-embedding-3-small: 1536
  • OpenAI text-embedding-3-large: 3072
  • Cohere embed-english-v3.0: 1024
  • Voyage voyage-3-large: 1024
  • BGE M3: 1024
Get the dimension wrong and inserts will fail with expected N dimensions, not M. A vector column without an index does a sequential scan for every search: fine for thousands of rows, slow above tens of thousands. Two index types: HNSW and IVFFlat. HNSW (Hierarchical Navigable Small World) is what Powabase uses internally and what most production setups want. It gives faster searches, slower builds, and no parameter tuning at query time:
The double cast embedding::vector(1536) is what pgvector wants for HNSW with explicit dimension typing. Without it the index may not be picked up at query time. vector_cosine_ops is the distance operator class:
  • vector_cosine_ops: cosine distance (most common for text embeddings)
  • vector_l2_ops: Euclidean distance
  • vector_ip_ops: inner product (negative dot product)
Match the distance to whatever your embedding model recommends. Most text embedding models are L2-normalized, so cosine and inner-product give the same ranking; for non-normalized embeddings the choice matters. IVFFlat is an older index type: faster to build, slower to query, needs lists tuning:
lists is roughly rows / 1000 for the rule-of-thumb default. Use HNSW unless you have a specific reason; it’s strictly better for most workloads.

Inserting embeddings

You compute the embedding in your application code (calling OpenAI, Cohere, etc.) and insert via PostgREST or SQL:
The embedding column expects a JSON number array; PostgREST converts it to pgvector’s internal format on insert.

Searching

Vector search uses the distance operators (<=> for cosine, <-> for L2, <#> for negative inner product):
The 1 - (embedding <=> $1) converts cosine distance (lower = more similar) to similarity (higher = more similar) for client-friendly results. Via PostgREST RPC:
Call from your app:
The RPC pattern keeps the query logic in the database and lets you index/optimize it independently of your app code.

Hybrid search (vector + BM25)

For RAG-quality retrieval, combine vector similarity with BM25 keyword scoring. You’ll need a tsvector column and matching GIN index:
Then combine in an RPC:
This is a basic weighted-sum hybrid; the typed KB surface uses reciprocal rank fusion (RRF), which holds up better across query types. For real RAG quality, the typed surface is still the right answer.

When pgvector isn’t enough

For very large corpora (10M+ embeddings), even HNSW gets slow. Three options:
  1. Use the typed KB surface. Powabase’s KB indexing strategies handle large corpora with techniques like PageIndex and GraphIndex that go beyond flat vector search.
  2. Specialized vector DB: Pinecone, Weaviate, Qdrant. These add cost but are sometimes the right shape for billion-scale workloads.
  3. Quantization or dimensionality reduction. Reduce the embedding size to 256 or 512 dimensions; faster but lower recall.
For most apps under 1M embeddings, user-managed pgvector is fine.

Next steps

Sources reference

The typed surface for document ingestion if you decide pgvector-direct is too much work.

Knowledge Bases reference

The typed search surface with hybrid retrieval and reranking.

Extensions

What else is preloaded alongside pgvector.

Direct Postgres

For the SQL patterns this guide builds on.