Supabase ↔ AI: Vector Search and Embeddings for No-Code Builders (2026)


Supabase will happily store your AI embeddings. Getting them back out as a similarity search is another matter: every documented path runs through SQL and a Postgres function, not a node you click. This guide maps the three routes, their documented limits, and the two traps that make results quietly wrong.

What You’re Actually Building (and Why It Isn’t a Supabase Feature)

Supabase describes its AI offering as an open source toolkit for developing AI applications: a vector store and embeddings support on Postgres and pgvector, embedding generation in Edge Functions, migrations for structured embeddings, and integrations with providers such as OpenAI, Hugging Face, LangChain, Amazon Bedrock, Fireworks AI, and LlamaIndex. The AI overview lists three search types: semantic (search by meaning), keyword (search by words), and hybrid (both combined). It also documents hybrid search concretely: a tsvector keyword column with a GIN index, merged with vector results using Reciprocal Ranked Fusion, where a record ranked third in keyword search and ninth in semantic search scores 1/3 + 1/9 = 0.444.

An embedding captures the “relatedness” of text, images, or video as a list of numbers. The documented use cases are search, recommendations, classifications, and clustering.

Here is the part the marketing does not lead with. Across every documented route, exactly one step is pure clicking: enabling the vector extension in the Dashboard, via Database → Extensions → search “vector”. You can also create the table in the Dashboard’s Table Editor, but the matching function and the indexes are SQL you paste. This is Postgres machinery wearing a friendly dashboard.

What a Vector Column Is, in Plain Language

An embedding turns a piece of text into a fixed-length list of numbers. Texts with similar meanings produce lists with similar values. Store the list in a table column, and “find similar documents” becomes “find the closest lists of numbers”. That comparison is what pgvector does.

pgvector is an open-source Postgres extension for vector similarity search. It supports exact and approximate nearest neighbor search, single-precision, half-precision, binary, and sparse vectors, and several distance measures including L2, inner product, and cosine.

Supabase enables it with one click: Database page, Extensions in the sidebar, search for “vector”. After that, a new vector data type is available, and the docs’ example declares a column as embedding extensions.vector(384). The number in parentheses is the dimension count. That number is not decoration. It must match your embedding model’s output exactly. The open-source gte-small model outputs 384 dimensions, a fact repeated in the concepts guide. Pick a different model, change the number to match.

Two rules from the docs worth memorizing. First: “In general, embeddings with fewer dimensions perform best.” Second, you must use the same embedding model for every stored vector and every query vector; comparing embeddings from different models “will yield meaningless results” (semantic-search guide).

The Wall: Why Your No-Code Tool Can’t Just “Query Similarity”

Now the wall. Supabase’s own docs state that PostgREST does not currently support pgvector similarity operators. PostgREST is the API layer your no-code tools actually talk to. It reads and writes rows. It cannot express “find the rows whose vectors are closest to this one”, because the operators that do that are Postgres operators, not URL parameters.

The documented workaround: wrap the similarity query in a Postgres function and call it with the rpc() method.

The consequence for a no-code builder is concrete. The n8n Supabase node supports one operation group, Row: “Create a new row”, “Delete a row”, “Get a row”, “Get all rows”, “Update a row”. There is no vector operation and no RPC operation. n8n’s documentation says that if the node doesn’t support what you want, use the HTTP Request node to call the service’s API.

So there is no documented click-path from “documents in Supabase” to “return the most similar documents”. Every path goes through SQL you write and an RPC call you configure.

The Three Routes to a Working “Chat With My Docs”

All three routes share the same SQL foundation: a table with a vector column, a match function, and optionally an index. They differ in where embeddings come from and how much configuration you tolerate.

A: Dashboard SQL + HTTP node B: Edge Function (gte-small) C: Client SDK / rpc()
Who it’s for Builder who can paste SQL once and configure an HTTP node Builder with CLI tolerance or a developer on call Developer, or a platform that allows custom code
SQL you must write Table + match function + index Same SQL, plus TypeScript function code Same table + function SQL
Embeddings come from An external provider you call yourself Built-in gte-small (384 dims), the only Edge Runtime model External provider; must match column dimensions
Documented gotchas Supabase node does row operations only; HTTP node needs API basics; node reads public schema only Cold starts possible; short-lived idempotent operations; Supabase CLI required locally PostgREST can’t run pgvector operators; .eq() after rpc() filters after ranking
Pure-click steps Enabling the vector extension only Extension enabling only; deploying the function needs the CLI None

Route A: Dashboard SQL Editor, Then an HTTP Node

Paste the SQL (table, function, index) into the Dashboard’s SQL editor. The only click-only step remains enabling the extension. Then, in n8n, call the function with the HTTP Request node, since the Supabase node cannot do it. The HTTP node supports POST among its methods, offers Header auth among its generic credential options, works with the predefined credential type n8n recommends where available, and can be configured by importing a curl command. n8n’s docs include a caveat worth reading twice: you “need some understanding of basic API terminology and concepts.” Two more documented details: the Supabase node only fetches the public schema by default, and the HTTP node is the stated answer for anything the Supabase node won’t do.

On Make.com: no Make documentation was in our source set. We can’t tell you whether Make ships a suitable module, so verify before committing to this route there.

Route B: Supabase Edge Function With Built-in gte-small

Edge Functions are server-side TypeScript functions running on Deno, distributed globally. Supabase’s embedding quickstart documents generating embeddings inside one: new Supabase.ai.Session('gte-small'), then running input with the mean_pool and normalize options. This is the only text embedding model supported in the Edge Runtime at the time of writing. OpenAI or Hugging Face embeddings are third-party APIs called from outside Supabase; they are not built in.

The constraints are documented too: cold starts are possible, functions are designed for short-lived, idempotent operations, and heavy long-running jobs belong in background workers. The quickstart’s workflow assumes the Supabase CLI installed locally, with commands like supabase functions new embed and supabase functions serve. That is a toolchain to learn before you have an embedding.

Route C: Client SDK, Calling rpc()

supabase-js connects to Postgres through PostgREST, which is why vector queries go through rpc('match_documents', ...) instead of a table read (vector-columns guide). This is the route for a developer friend, or for a no-code platform that lets you drop into custom code. Our sources do not establish that any specific no-code platform ships native vector support, so we make no such claim. If yours advertises one, verify it exposes rpc() and not just row reads.

The match_documents Pattern, Explained

Plain English first. The documented pattern is a SQL function named match_documents with three inputs. query_embedding is the vector of whatever you’re searching with, such as the embedding of the user’s question. match_threshold is the minimum similarity to return; the docs describe it as a value between 1 and -1, where 1 is most similar and -1 is most dissimilar, and warn that “this value will vary for each application - you will need to perform your own testing to determine the threshold that makes sense for your app.” match_count is the maximum number of results; the documented variant clamps it with limit least(match_count, 200), capped “to avoid unintentionally overloading your database”. Similarity is computed as 1 - (embedding <=> query_embedding): cosine distance subtracted from 1, so bigger means more similar.

Supabase’s guides document three of pgvector’s distance operators: <-> for Euclidean distance, <#> for negative inner product, and <=> for cosine distance. The docs’ guidance: “Cosine distance is a safe default when you don’t know whether or not your embeddings are normalized,” and “Dot product tends to be the fastest if your vectors are normalized.”

One warning that saves a support ticket. If you index the vector column, the function’s order by must sort by the distance expression itself, never by the calculated similarity column; the docs state sorting by that calculated column “may lead to the index being ignored and poor performance.”

Here is the function from the vector-columns guide:

create or replace function match_documents (
  query_embedding extensions.vector(384),
  match_threshold float,
  match_count int
)
returns table (
  id bigint,
  title text,
  body text,
  similarity float
)
language sql stable
as $$
  select
    documents.id,
    documents.title,
    documents.body,
    1 - (documents.embedding <=> query_embedding) as similarity
  from documents
  where 1 - (documents.embedding <=> query_embedding) > match_threshold
  order by (documents.embedding <=> query_embedding) asc
  limit match_count;
$$;

Read it once: the threshold filters, the order by sorts on raw distance so the index stays usable, the limit caps results. And here is the client call:

const { data: documents } = await supabaseClient.rpc('match_documents', {
  query_embedding: embedding, // Pass the embedding you want to compare
  match_threshold: 0.78, // Choose an appropriate threshold for your data
  match_count: 10, // Choose the number of matches
})

Indexes: HNSW vs IVFFlat, Without the Marketing

Without an index, a vector query scans every row. The docs call that “resource-intensive” once you have many records, which is a polite way of saying your search gets slow as the table grows.

pgvector supports two index types, HNSW and IVFFlat. Supabase recommends HNSW “because of its performance and robustness against changing data”. Per the HNSW guide, HNSW is an approximate nearest neighbor algorithm built on proximity graphs: Hierarchical (it operates over multiple layers) plus Navigable Small World (each vector is a node connected to several others), with skip lists offering O(log n) average complexity.

Know the dimension ceilings. For pgvector 0.7.0 and above, indexes support vector columns up to 2,000 dimensions, halfvec up to 4,000, and bit up to 64,000. Beyond 2,000 dimensions, the documented pattern casts the column to halfvec inside the index; the docs’ example does this for a 3,072-dimension column. The index must also use the operator class matching your distance operator: vector_l2_ops for Euclidean, vector_ip_ops for inner product, vector_cosine_ops for cosine.

HNSW IVFFlat
Supabase’s position Recommended “because of its performance and robustness against changing data” Listed as supported; no recommendation in our sources
How it works (documented) Approximate nearest neighbor via proximity graphs; Hierarchical + Navigable Small World; O(log n) average complexity via skip lists Divides vectors into lists, then searches the subset of lists closest to the query vector; faster builds and less memory than HNSW, lower query performance
Max indexed dimensions (pgvector 0.7.0+) vector: 2,000; halfvec: 4,000; bit: 64,000; halfvec cast above 2,000 Same limits, stated index-type-agnostically in our sources
Tuning parameters (pgvector README) m 16 and ef_construction 64 by default; hnsw.ef_search defaults to 40 (docs set 100 in examples) lists ≈ rows/1000 up to 1M rows, sqrt(rows) above; probes ≈ sqrt(lists); ivfflat.probes defaults to 1 (examples set 10)
When you can build it Immediately after creating the table — there is no training step Needs data first: IVFFlat has a training step

Our sources contain no benchmark latencies and no recall figures for either index type, so we will not print any. What they do say is directional: HNSW has the better speed-recall trade-off with slower builds and more memory, IVFFlat builds faster and uses less memory with worse query performance. If a tutorial quotes you precise millisecond numbers, ask where they came from.

Filtering: Exactly Where the No-Code Route Breaks

Here is the trap, and it leads this section on purpose. In supabase-js it feels natural to call rpc('match_documents') and then chain .eq('category', 'docs') to narrow by a column. Per the semantic-search docs, PostgREST applies that chained filter as an outer filter on the function’s result, after the function has already executed its similarity ranking and its limit. The vector planner never sees your filter. With a selective filter, you get fewer than match_count rows back, silently.

The builder blames vector search. Vector search is behaving correctly. The pattern is wrong.

The documented fix: push the filter into the SQL function itself, so Postgres can combine it with the vector predicate during planning. The semantic-search guide shows a match_documents variant with an extra typed parameter, filter_category text, and a where documents.category = filter_category line ahead of the distance condition; the same pattern works for a jsonb metadata column using the @> containment operator. Even then, a selective filter and an approximate index interact badly: the pgvector README documents that filters are applied after the index scan, so with a where clause matching 10% of rows and the default hnsw.ef_search of 40, only about 4 rows match on average. The documented remedy is the one the semantic-search guide gives: from pgvector 0.8.0 the planner supports iterative index scans that re-enter the index to gather more candidates once a selective post-filter drops the result set below match_count. Check the version before you rely on it.

Carry the earlier warning here too: if you modify the function or write your own variant, keep any order by on the distance expression. Sorting by the computed similarity column can push Postgres off the index entirely.

Pricing Context (and What the Pricing Page Doesn’t Tell You)

Supabase’s pricing page lists three self-serve tiers: Free at $0 per month, Pro at $25 per month, and Team from $599 per month. The Free tier carries 500 MB of database, 5 GB of egress, 50,000 monthly active users and a limit of two active projects, and free projects pause after a week of inactivity. Pro adds 8 GB of disk per project, 250 GB of egress, 100,000 monthly active users, seven days of daily backup retention and seven-day log retention. Team adds SSO, project-scoped read-only access and SOC2/ISO 27001. What the page does not itemize is embedding cost: no per-token or per-call figure for running inference in an Edge Function appears in any source behind this guide, so budget that yourself.

One cost the page will not itemize: once you adopt this stack, you own Postgres objects. Tables, the match_documents function, indexes, and the discipline of keeping one embedding model across your whole corpus. The exit cost is not the subscription. It is the SQL you now maintain.

What This Guide Did NOT Test

For the record: nothing in this guide was hands-on tested. No queries were run against a Supabase project, no benchmarks were performed, and no latency, recall, or cost numbers were measured. Not covered, because no source covered it: real query latency, recall or precision for HNSW or IVFFlat, embedding costs, Row Level Security behavior of rpc(), Edge Function rate limits, chunking strategies, and whether the 200-record cap in match_documents is configurable or a hard-coded literal. Where a section above says “not established by our sources”, that is a genuine gap, filled with nothing.

FAQ

Can I build this with zero SQL?

No. Every documented route requires SQL for the table and the match function; the only pure-click step is enabling the vector extension via the Dashboard (Database → Extensions → search “vector”). Route A hides the SQL in a one-time paste; it does not remove it. If you cannot paste SQL at all, none of the documented routes work.

Can I use OpenAI embeddings instead of gte-small?

Yes, but they come from OpenAI’s API, called from outside Supabase; gte-small is the only text embedding model built into the Edge Runtime. Your vector column’s dimensions must match the model’s output exactly. Never mix embeddings from different models in one corpus, because comparisons become meaningless.

Why do my filtered searches return fewer results than I asked for?

Because a filter chained after rpc(), like an .eq() call, is applied by PostgREST as an outer filter, after the function has already ranked and limited its results. A selective filter can cut the returned list below match_count. The documented fix is to add the filter inside the SQL function.

HNSW or IVFFlat?

Supabase recommends HNSW for its performance and robustness against changing data. Our sources contain no benchmark numbers and no latency figures for either index type, so there is nothing measured to add beyond that recommendation; the tuning starting points in the table above (HNSW m/ef_construction/ef_search, IVFFlat lists/probes) are the pgvector README’s documented defaults and heuristics, not tested values.

Do Zapier, Make, or Airtable have a native vector node?

Not established by our sources. No Make.com documentation was available to us, and we make no claim either way about Zapier or Airtable. The one documented automation path is n8n’s HTTP Request node calling your match function; assume any other platform needs the same verification.

How This Guide Was Built

This guide was compiled from published documentation, with figures read off the official pages on 2026-11-07: Supabase’s AI overview, vector-columns, semantic-search, concepts, generate-text-embeddings quickstart, vector-indexes, HNSW indexes, hybrid-search, and Edge Functions guides; pgvector’s GitHub README; n8n’s Supabase node and HTTP Request node pages; and Supabase’s pricing page. Nothing was hands-on tested. No queries were run, no benchmarks were performed, and no latency, recall, or cost figures were measured by us. Where the sources were silent, this guide says so instead of guessing.

The Bottom Line

Supabase’s vector stack is capable and unusually portable: pgvector is open source, your vectors live in ordinary Postgres columns, and the match function is plain SQL you can read and take with you. But call it what it is. This is low-code with a cliff, and the cliff is a SQL function. PostgREST cannot run the similarity operators, so no route avoids writing SQL, and two documented traps (the .eq() outer filter and the similarity-column sort) produce quietly wrong results if you stumble into them. If you can paste SQL, or can briefly pay someone who can, this is a solid foundation that avoids the worst lock-in because everything lives in your own Postgres. If you cannot, no amount of Dashboard clicking will produce a similarity search. Budget for a developer, or verify a different tool before you buy; our sources say nothing about those alternatives.

  • ToolBrain — tool reviews, LLM comparisons, and AI workflow guides
  • CodeIntel Log — code quality, debugging, and software engineering benchmarks

Cross-links automatically generated from NoCode Insider.