.textSearch()) and/or Supabase Vector move their search workload to Meilisearch.
For a high-level comparison with PostgreSQL-based search, see Meilisearch vs PostgreSQL.
Overview
Meilisearch is not a replacement for Supabase. It is a dedicated search engine designed to sit alongside your Supabase database. The recommended pattern is to keep Supabase as your source of truth and sync data to Meilisearch for search. Supabase exposes PostgreSQL’s built-intsvector/tsquery full-text search through its client libraries (.textSearch() method) and uses the pgvector extension for vector similarity search (Supabase Vector). While convenient, these inherit all of PostgreSQL’s search limitations: no typo tolerance, no prefix search by default, no built-in relevancy ranking, and manual configuration of tsvector columns and GIN indexes.
This guide walks you through exporting rows from Supabase and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. You can also skip directly to the finished script.
The migration process consists of four steps:
- Export your data from Supabase
- Prepare your data for Meilisearch
- Import your data into Meilisearch
- Configure your Meilisearch index settings (optional)
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
- JavaScript:
@supabase/supabase-js,meilisearch(compatible with Meilisearch v1.0+) - Python:
supabase,meilisearch - Ruby:
pg,meilisearch(there is no official Supabase Ruby client — connect directly to PostgreSQL using your Supabase connection string)
Export your Supabase data
Initialize project
Install dependencies
Create Supabase client
You need your Supabase project URL and service role key (not the anon key, since the service role key bypasses Row Level Security and can read all rows). For Ruby, use the direct database connection string from your Supabase project settings.Fetch data from Supabase
Use range-based pagination to retrieve all rows. The Supabase client’s.range(from, to) method returns up to 1,000 rows per request by default.
YOUR_TABLE_NAME with the name of the table you want to migrate. If your table does not have an id column, replace it with your primary key column name in the Ruby example.
For very large tables (millions of rows), consider exporting data using the Supabase CLI (
supabase db dump) or connecting directly to PostgreSQL to use the COPY command.Prepare your data
Supabase rows returned by the JavaScript and Python clients are already JSON objects, so they map naturally to Meilisearch documents. You mainly need to ensure a primary key field exists, remove any derivedtsvector columns (they cannot be serialized), and handle any embedding vector columns from Supabase Vector.
If your primary key column is not called
id, you can either rename it in the preparation step (as shown above) or tell Meilisearch which field to use as the primary key when creating the index. Replace your_pk_column with the actual column name.Handle PostGIS geo data
If your Supabase table uses PostGIS geography or geometry columns, convert them to Meilisearch’s_geo format. You need to extract coordinates from PostGIS. For JavaScript and Python, add a database function or use the direct PostgreSQL connection. For Ruby, modify the SQL query:
Import your data into Meilisearch
Create Meilisearch client
Create a Meilisearch client by passing the host URL and API key of your Meilisearch instance. The easiest option is to use the automatically generated admin API key.MEILI_HOST, MEILI_API_KEY, and MEILI_INDEX_NAME with your Meilisearch host URL, API key, and target index name. Meilisearch will create the index if it doesn’t already exist.
Upload data to Meilisearch
Use the Meilisearch client methodaddDocumentsInBatches to upload all records in batches of 100,000.
Finished script
Configure your index settings
Meilisearch’s default settings deliver relevant, typo-tolerant search out of the box. Unlike Supabase, where.textSearch() is syntactic sugar over PostgreSQL’s to_tsquery() and requires tsvector columns and GIN indexes, Meilisearch indexes all fields automatically and handles tokenization, stemming, and typo tolerance without any configuration.
To customize your index settings, see configuring index settings. To understand the differences between Supabase search and Meilisearch, read on.
Key conceptual differences
Supabase full-text search is a convenience layer over PostgreSQL’s built-in search. The.textSearch() client method translates to to_tsquery() under the hood. You still need tsvector columns, GIN indexes, and language configurations. There is no typo tolerance, no prefix search by default, and relevancy ranking requires manual ts_rank() calls.
Supabase Vector uses the pgvector extension to store and query vector embeddings. You must generate embeddings in your application code or Supabase Edge Functions, store them in a vector column, and write RPC functions like match_documents() to perform similarity search. This adds significant complexity to your stack.
Meilisearch is a dedicated search engine. You send documents and search queries — everything else is automatic. Tokenization, stemming, typo tolerance, prefix search, and ranking all work out of the box. Because Meilisearch runs as a separate service, search queries never impact your Supabase database performance.
Configure embedders for hybrid search
If you currently use Supabase Vector for semantic similarity search, you can replace the entire pipeline — embedding generation in Edge Functions, vector columns, RPC functions, pgvector indexes — with Meilisearch’s built-in hybrid search. Configure an embedder and Meilisearch handles all vectorization automatically, both at indexing time and at search time.documentTemplate controls what text is sent to the embedding model. Adjust it to match the fields in your documents. With this single configuration, you can remove:
- Supabase Edge Functions that generate embeddings
- The
embeddingvector column from your table - The
match_documents()RPC function - Any pgvector indexes (ivfflat or hnsw)
- Client-side embedding generation code
Alternative: use existing pgvector embeddings
Alternative: use existing pgvector embeddings
If you already have embeddings stored in a pgvector Replace
vector column and prefer not to re-embed, export them from Supabase and include them in the _vectors field of each document. Then configure a userProvided embedder:1536 with the dimension of your pgvector embeddings. With this approach, you remain responsible for computing and providing vectors when adding or updating documents, and for computing query vectors client-side when searching.Configure filterable and sortable attributes
In Supabase, any column can be used with.eq(), .gt(), .lt(), and .order(). In Meilisearch, you must declare which fields are filterableAttributes and sortableAttributes:
What you gain
Migrating your search layer from Supabase to Meilisearch gives you several features that work out of the box:- Typo tolerance — Supabase’s
.textSearch()inherits PostgreSQL’s zero typo tolerance. A single typo returns zero results. Meilisearch handles typos automatically, so “reciepe” finds “recipe” - Prefix search — Users see results as they type, without needing trigram indexes or
LIKEqueries - Instant results — Sub-50ms search responses regardless of dataset complexity, with no GIN index tuning
- Highlighting of matching terms in results, without manually calling
ts_headline()via RPC - Faceted search with value distributions for building filter UIs — no
GROUP BYqueries or RPC functions needed - Hybrid search combining keyword relevancy and semantic similarity in a single query, replacing separate
.textSearch()andmatch_documents()pipelines - No search infrastructure in your database — Remove
tsvectorcolumns, GIN indexes, embedding columns, pgvector indexes, RPC functions, and Edge Functions for embedding generation. Your Supabase database handles what it does best (transactions and relational data), while Meilisearch handles search
Settings and parameters comparison
Supabase client methods
| Supabase client | Meilisearch | Notes |
|---|---|---|
.textSearch(column, query) | q search param | Just send the user’s text — no tsquery construction needed |
.eq(column, value) | filter with = | Requires filterableAttributes |
.gt() / .gte() / .lt() / .lte() | filter with >, >=, <, <= | Requires filterableAttributes |
.in(column, values) | filter with IN [v1, v2] | Requires filterableAttributes |
.order(column, { ascending }) | sort search param | Requires sortableAttributes |
.range(from, to) | offset / limit or page / hitsPerPage | Search params |
.select(columns) | attributesToRetrieve | Search param |
.limit(count) | limit | Search param |
| No equivalent | attributesToHighlight | Highlight matching terms in results |
| No equivalent | facets | Get value distributions for fields |
| No equivalent | hybrid | Combined keyword + semantic search |
Supabase Vector (pgvector)
| Supabase Vector | Meilisearch | Notes |
|---|---|---|
match_documents() RPC function | hybrid + auto-embedder | No RPC functions needed — just send a text query |
pgvector <=> cosine operator | Automatic via configured embedder | Distance metric handled internally |
embedding vector column | Not needed with auto-embedder | Meilisearch generates and stores vectors automatically |
| Embedding generation in Edge Functions | Automatic via configured embedder | Remove all embedding generation code |
vecs Python library | meilisearch Python SDK with hybrid | Single SDK for all search types |
| hnsw / ivfflat index on vector column | Automatic (DiskANN-based) | No index type selection needed |
match_count parameter | limit search param | Search param |
PostgreSQL concepts (underlying Supabase)
| PostgreSQL concept | Meilisearch | Notes |
|---|---|---|
to_tsvector(config, text) | Automatic tokenization | No text processing functions needed |
to_tsquery() / plainto_tsquery() | q search param | Just send the user’s text |
ts_rank() / ts_rank_cd() | Built-in ranking rules | Relevancy ranking is automatic and configurable |
tsvector column + GIN index | Automatic | Meilisearch indexes all fields automatically |
Language configurations (english, french) | localizedAttributes | Assign languages to specific fields |
setweight() (A, B, C, D) | searchableAttributes | Ordered list — fields listed first have higher priority |
tsvector update triggers | Automatic | Meilisearch re-indexes on every document update |
| No typo tolerance | Automatic typo tolerance | Configurable per index |
Query comparison
This section shows how common Supabase search operations translate to Meilisearch. All Supabase examples use the JavaScript client syntax (the most widely used). Meilisearch examples are shown as JSON POST requests.Full-text search
Supabase:tsvector columns, no @@ operator, no ts_rank() function. Just send the text. Meilisearch also handles typos — searching for “runnign shoes” still returns the right results.
Filtered search
Supabase:Attributes used in
filter must first be added to filterableAttributes.Sorting
Supabase:Attributes used in
sort must first be added to sortableAttributes.Vector / semantic search
Supabase (requires Edge Function for embedding + RPC function):q text for you. No client-side embedding generation, no Edge Functions, no RPC functions. Setting semanticRatio to 1.0 performs pure semantic search. Use a value like 0.5 to combine keyword and semantic results in a single hybrid query.
Faceted search
Supabase (requires a custom RPC function):GROUP BY queries needed.
Geo search
Supabase (requires PostGIS + RPC function):The
_geo attribute must be added to both filterableAttributes and sortableAttributes.Keeping data in sync
Since Supabase remains your source of truth, you need a strategy to keep Meilisearch in sync when data changes. Supabase offers several built-in mechanisms that make this straightforward.Database Webhooks
Supabase Database Webhooks trigger an HTTP request on INSERT, UPDATE, or DELETE events. Point them at a serverless function that updates Meilisearch:- Go to Supabase Dashboard > Database > Webhooks
- Create a webhook for your table, selecting the events you want to track
- Set the URL to a serverless function (Supabase Edge Function, Vercel, etc.) that forwards the change to Meilisearch
Supabase Edge Functions
Create an Edge Function that receives webhook payloads and syncs changes to Meilisearch:Supabase Realtime
Subscribe to database changes from your application and sync them as they happen:Periodic batch sync
Run a scheduled job that queries Supabase for recently modified rows:addDocuments method is an upsert — sending an existing document with the same primary key updates it automatically.