tsvector/tsquery) and/or the pgvector extension move their search workload to Meilisearch.
For a high-level comparison of the two, see Meilisearch vs PostgreSQL.
Overview
Meilisearch is not a database replacement. It is a dedicated search engine designed to sit alongside PostgreSQL. The recommended pattern is to keep PostgreSQL as your source of truth and sync data to Meilisearch for search. This guide walks you through exporting rows from a PostgreSQL table 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 PostgreSQL
- 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:
pg(node-postgres),meilisearch(compatible with Meilisearch v1.0+) - Python:
psycopg2,meilisearch - Ruby:
pg,meilisearch
Export your PostgreSQL data
Initialize project
Install dependencies
Create PostgreSQL client
You need your PostgreSQL connection string or individual connection parameters (host, database, user, password). Paste the below code in your script:Fetch data from PostgreSQL
Query your table to retrieve all rows. For large tables, use cursor-based pagination to avoid loading everything into memory at once.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.
For very large tables (millions of rows), consider using a server-side cursor or
COPY command to export data to a JSON file, then import that file into Meilisearch.Prepare your data
PostgreSQL rows are already flat key-value pairs, so they map naturally to Meilisearch documents. You mainly need to ensure a primary key field exists and convert any PostgreSQL-specific types.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 table uses PostGIS geography or geometry columns, convert them to Meilisearch’s_geo format. Export the coordinates from PostgreSQL using ST_Y() (latitude) and ST_X() (longitude):
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 PostgreSQL, where you must createtsvector columns, build GIN indexes, and construct queries with to_tsquery(), 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 PostgreSQL full-text search and Meilisearch, read on.
Key conceptual differences
PostgreSQL full-text search requires you to manage every aspect of the search pipeline manually. You must createtsvector columns (or expressions), build GIN indexes for performance, choose language configurations for stemming and stop words, construct queries with to_tsquery() or plainto_tsquery(), and rank results with ts_rank(). Search is tightly coupled to your database, competing for the same resources as your transactional queries.
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 database performance.
The most important difference: PostgreSQL has no typo tolerance. A search for “reciepe” returns zero results even if your table contains hundreds of recipes. Meilisearch handles typos automatically, making it dramatically more forgiving for end users.
Configure embedders for hybrid search
If you currently use pgvector for semantic similarity search, you can replace it with Meilisearch’s built-in hybrid search. Configure an embedder and Meilisearch handles all vectorization automatically — both at indexing time and at search time. No more managing embeddings in your application code.documentTemplate controls what text is sent to the embedding model. Adjust it to match the fields in your documents. For more options including HuggingFace models, Ollama, and custom REST endpoints, see configuring embedders.
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 PostgreSQL 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 PostgreSQL, any column can be used inWHERE and ORDER BY clauses. In Meilisearch, you must declare which fields are filterableAttributes and sortableAttributes:
What you gain
Migrating your search layer from PostgreSQL to Meilisearch gives you several features that work out of the box:- Typo tolerance — PostgreSQL full-text search has none. A single typo returns zero results. Meilisearch handles typos automatically, so “reciepe” finds “recipe”
- Prefix search — Users see results as they type, without needing
LIKE 'term%'queries or trigram indexes - Instant results — Sub-50ms search responses regardless of dataset complexity, with no GIN index tuning or query plan optimization
- Highlighting of matching terms in results, without manually calling
ts_headline() - Faceted search with value distributions for building filter UIs — no
GROUP BYqueries needed - Hybrid search combining keyword relevancy and semantic similarity in a single query, replacing separate pgvector and
tsvectorpipelines - No search infrastructure in your database — Remove
tsvectorcolumns, GIN indexes, triggers, and ranking functions. Your PostgreSQL database handles what it does best (transactions and relational data), while Meilisearch handles search
Settings and parameters comparison
Text search configuration
| PostgreSQL | Meilisearch | Notes |
|---|---|---|
tsvector column + GIN index | Automatic | Meilisearch indexes all fields automatically — no columns or indexes to create |
to_tsvector(config, text) | Automatic tokenization | No text processing functions needed |
ts_rank() / ts_rank_cd() | Built-in ranking rules | Relevancy ranking is automatic and configurable |
Language configurations (english, french, etc.) | localizedAttributes | Assign languages to specific fields |
setweight() (A, B, C, D) | searchableAttributes | Ordered list — fields listed first have higher priority |
| Custom dictionaries | synonyms / stopWords | Configure equivalent terms and ignored words |
tsvector update triggers | Automatic | Meilisearch re-indexes on every document update |
Search queries
| PostgreSQL | Meilisearch | Notes |
|---|---|---|
to_tsquery() / plainto_tsquery() / websearch_to_tsquery() | q search param | Just send the user’s text — no query construction needed |
@@ operator | Automatic | No matching operator — q handles it |
WHERE column = value | filter search param | Requires filterableAttributes |
ORDER BY column | sort search param | Requires sortableAttributes |
LIMIT / OFFSET | limit / offset or page / hitsPerPage | Search params |
ts_headline() | attributesToHighlight | Search param — returns highlighted snippets automatically |
COUNT(*) | estimatedTotalHits / totalHits | Returned in every search response |
ILIKE '%term%' | q with prefix search | Automatic prefix matching on the last word |
| No typo tolerance | Automatic typo tolerance | Configurable per index |
Vector search (pgvector)
| PostgreSQL (pgvector) | Meilisearch | Notes |
|---|---|---|
ORDER BY embedding <=> query_vector (cosine) | hybrid + auto-embedder | Meilisearch embeds queries for you — no client-side vector computation |
ORDER BY embedding <-> query_vector (L2) | hybrid + auto-embedder | Distance metric is handled automatically |
vector type + ivfflat / hnsw index | embedders setting | Automatic indexing (DiskANN-based) — no index type selection needed |
| Manual embedding generation in application code | Automatic via configured embedder | Meilisearch embeds documents and queries for you |
| Separate keyword + vector queries | Single hybrid query | Combines keyword and semantic search in one request |
Geo search (PostGIS)
| PostgreSQL (PostGIS) | Meilisearch | Notes |
|---|---|---|
ST_DWithin(geog, ST_MakePoint(lng, lat), distance) | _geoRadius(lat, lng, distance) in filter | Requires _geo in filterableAttributes |
ST_MakeEnvelope(xmin, ymin, xmax, ymax) | _geoBoundingBox([lat, lng], [lat, lng]) in filter | Requires _geo in filterableAttributes |
ORDER BY ST_Distance(geog, point) | _geoPoint(lat, lng):asc in sort | Requires _geo in sortableAttributes |
geography / geometry types | _geo field with lat / lng | Simple JSON object |
Query comparison
This section shows how common PostgreSQL search queries translate to Meilisearch.Full-text search
PostgreSQL:tsvector columns, no @@ operator, no ts_rank() function. Just send the text.
Filtered search
PostgreSQL:Attributes used in
filter must first be added to filterableAttributes.Sorting
PostgreSQL:Attributes used in
sort must first be added to sortableAttributes.Highlighting
PostgreSQL:Geo search
PostgreSQL (PostGIS):The
_geo attribute must be added to both filterableAttributes and sortableAttributes.Semantic search
PostgreSQL (pgvector):q text for you. No client-side vector computation. 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
PostgreSQL:GROUP BY queries needed.
Keeping data in sync
Since PostgreSQL remains your source of truth, you need a strategy to keep Meilisearch in sync when data changes. Common approaches:- Application-level sync — After every INSERT, UPDATE, or DELETE in your application code, send the corresponding change to Meilisearch. This is the simplest approach and works well for most applications
- Database triggers with notifications — Use PostgreSQL
LISTEN/NOTIFYto broadcast changes, then have a worker process consume notifications and update Meilisearch - Periodic batch sync — Run a scheduled job (every few minutes) that queries PostgreSQL for recently modified rows (using an
updated_attimestamp) and sends them to Meilisearch - Change data capture (CDC) — Use tools like Debezium to stream PostgreSQL WAL changes to Meilisearch in near real-time
addDocuments method is an upsert — sending an existing document with the same primary key updates it automatically.