Meili CDC Docs

Create a task

A task connects one source table to one Meilisearch index. The wizard walks through six steps; every option it shows maps to a field of the task JSON used by the API and the tasks file.

Open Tasks → New task (admin role). The steps are Connector, Source, Mapping, Index, Sync & safety and Review. Errors are shown on the step they belong to, and the review lists anything left to fix.

1. Connector

Pick the saved connector to read from, or add one from the side panel without leaving the wizard. Credentials live in the connector only; the task never repeats them. See Connectors.

Wizard step one: choosing a saved connector, each card showing the source type and host
Step 1: choose a connector.

2. Source

Basics, then what to sync:

FieldJSONNotes
NamenameRequired.
Task ididOptional. Letters, digits, - and _, at most 64 characters. The wizard derives it from the name when empty; the API generates a random one. It cannot change later and names the replication slot, consumer group and webhook URL.
Description, tagsdescription, tagsTags filter the task list.
Table (or collection, topic, stream…)source.tableRequired. Pick from the tables the connector lists, or type it.
Source-specific fieldssource.schema, source.database, source.slot, source.publication, source.serverId, source.groupId, source.cursorColumn, source.pollInterval, source.webhookSecretShown only for the sources that use them; each source page explains them.

Test connection (moderator) connects through the connector, reads the table's schema and transforms five sample rows with the current mapping, so you see real documents before anything is written. It uses POST /api/v1/tasks/test. For PostgreSQL, testing also creates the task's publication if it does not exist yet.

3. Mapping

The mapping turns rows into documents.

Wizard mapping step with primary key columns, include, exclude and rename per column, soft delete, geo columns, time format and static fields
Step 3: document identity, fields, transforms and static fields.

Document identity

FieldJSONDefaultMeaning
Primary key columnsmapping.primaryKeythe source's primary keySource columns that identify a row. Several columns build a composite id. Required when the source has no primary key.
Primary key fieldmapping.primaryKeyFieldidThe document field that holds the id; it becomes the index's primary key.
Keep key columnsmapping.keepKeyColumnsoffAlso copy the original key columns into the document. Off: a key column is left out, unless it is the single key column and has the same name as the primary key field.

How document ids are built

  • A single integer key is written as a number (42).
  • Other single keys are written as strings. Values made only of a-z A-Z 0-9 - (integers, UUIDs, slugs) are kept as they are. Anything else is escaped into Meilisearch's id alphabet: _ becomes __ and every other byte becomes _XX (hex). For example user@example.com becomes user_40example_2Ecom. Escaped ids always contain _ and raw ones never do, so two different keys never collide.
  • Composite keys join their escaped parts with - (a - inside a part is escaped too): (7, "fr") becomes 7-fr.
  • Ids longer than 511 bytes are replaced by h- and a SHA-256 hex digest.
  • A row without a value for a key column cannot become a document; it goes to the dead letter queue as a transform entry and the checkpoint moves on.

Two tasks may write to the same index (with the upsert write mode, to merge fields from several tables), but only with the same primary key field: saving a task whose key field differs from another task on that index and destination fails with 409.

Fields

FieldJSONMeaning
Includemapping.includeCopy only these columns (key columns are always available for the id). Empty copies every column.
Excludemapping.excludeDrop these columns. Applied after include.
Renamemapping.renameMap of source column to document field, for example {"customer_email": "email"}.

Transforms

FieldJSONMeaning
Soft delete column and valuemapping.softDeleteColumn, mapping.softDeleteValueRows whose column matches turn into deletes. With an empty value, any non-null value deletes (a deleted_at timestamp, say), except boolean false. With a value, the column's text is compared case-insensitively, for example true or 1.
Geo latitude and longitude columnsmapping.geo.lat, mapping.geo.lngBuild Meilisearch's _geo field {"lat": …, "lng": …} from two numeric (or numeric string) columns. When either is missing or not a number, _geo is written as null. Add _geo to the filterable and sortable attributes to use geosearch. The source columns themselves are still copied unless excluded.
Time formatmapping.timeFormatrfc3339 (default; ISO 8601 strings in UTC, readable but not usable for range filters or chronological sorting), unix (seconds since the epoch: filterable and sortable numbers) or unix_ms (milliseconds).

Static fields

mapping.static adds the same fields to every document, for example {"tenant": "acme", "source": "eu-db"}. This is handy for multi-tenant indexes fed by several tasks. Static fields overwrite source columns of the same name; the primary key field always wins.

4. Index

Wizard index step with the destination cards, the index uid, the write mode and the index settings editor
Step 4: destination, index uid, write mode and managed settings.
FieldJSONDefaultMeaning
Destinationindex.destinationIdthe default destinationThe Meilisearch instance to write to.
Index uidindex.uidthe table nameRequired; a-z A-Z 0-9 - _, up to 400 characters. Created on first write when missing, with the primary key field as its primary key. The wizard suggests existing indexes and shows their document count and other writers.
Write modeindex.writeModereplacereplace sends whole documents (Meilisearch POST /documents): fields removed in the source disappear. upsert merges fields into existing documents (PUT /documents): useful when several tasks write one index, or when the source omits unchanged columns (PostgreSQL TOAST). In upsert mode a document deleted and re-created in the same batch is replaced, so stale fields do not come back.
Manage index settingsindex.manageSettingsoffApply index.settings when the task starts (and when you save it while stopped), and report drift in the schema check.
Settingsindex.settingsnoneMeilisearch index settings, the same JSON as Meilisearch's PATCH /indexes/{uid}/settings. Only the keys you set are managed. Edited with the settings editor. Unknown keys are rejected.

5. Sync & safety

Sync mode

ModeJSONBehaviour
Snapshot + streamsnapshot_stream (default)Copy the existing rows, then stream every change. The usual choice.
Stream onlystreamStart from the current position; existing rows are not copied. Resync is refused in this mode.
Snapshot onlysnapshotCopy the rows once; the task then shows completed and stops.

Batching

FieldJSONDefaultMeaning
Batch sizesync.batchSize1000Documents per Meilisearch request. Larger batches index faster and use more memory; 5,000 to 10,000 suits big snapshots.
Flush intervalsync.flushInterval1sThe longest a change waits in a partial batch. Lower it for lower latency on quiet tables.
Max in-flightsync.maxInflight4Batches sent to Meilisearch and not yet confirmed. Meilisearch auto-batches consecutive tasks, so 8 helps bulk loads.

Within a batch, changes to the same document are coalesced and only the final state is sent. Confirmations are processed in order, so checkpoints never skip an unconfirmed batch. See Performance and tuning.

Retry

FieldJSONDefaultMeaning
Max attemptssync.retry.maxAttempts8Attempts for one Meilisearch write before the pipeline gives up and restarts.
Initial backoffsync.retry.initialBackoff200msFirst wait; waits grow exponentially with full jitter.
Max backoffsync.retry.maxBackoff30sUpper bound of a wait.

Network errors, Meilisearch 5xx, 429 and 408 are retried. A payload that is too large is split in half. Documents Meilisearch rejects are isolated into the dead letter queue. Configuration errors (a bad API key, an index whose primary key differs) stop the task as failed. When the attempts run out, the pipeline restarts with its own backoff (1 s up to 1 minute) and the task shows retrying.

Safety guards

FieldJSONWizard defaultAPI defaultMeaning
Max delete ratiosync.safety.maxDeleteRatio0.2 (20%)0 (off)Hold the task when one flush would delete more than this share of the index (0 to 1; 0 disables the guard).
Min docs for delete guardsync.safety.minDocsForDeleteGuard10001000The ratio check is skipped while the index has fewer documents.
Allow TRUNCATEsync.safety.allowTruncateoffoffLet a source truncate (or a dropped MongoDB collection) delete every document. When off, the task is held.
Zero-downtime resyncsync.safety.zeroDowntimeResynconoffRebuild into a shadow index and swap it in when complete, so search keeps working during a resync.
Dump before resyncsync.safety.dumpBeforeResyncoffoffAsk Meilisearch for a dump before a zero-downtime rebuild starts (best effort: a failed dump is logged, the rebuild continues).

A held task has stopped before applying the risky batch, so nothing was lost and nothing was deleted. Look at the source, then Resume (the batch is retried, and holds again if the guard still applies), raise the guard, or Resync. The hold reason is in the task's status, for example safety hold: a single batch would delete 5000 of 8000 documents (62% > 20% limit) or safety hold: the source table was truncated; enable safety.allowTruncate or resync the task. The delete guard reads the index size at most every 10 seconds.

6. Review

The review summarises the task, shows its JSON and lists any field that needs attention. Create saves it stopped; Create & start starts it right away (API: POST /api/v1/tasks?start=true). Validation errors come back as 422 with one message per problem.

Task JSON reference

The full shape accepted by POST /api/v1/tasks, PUT /api/v1/tasks/{id}, import and the tasks file. Defaults are applied when a field is empty or zero.

{
  "id": "products",
  "name": "Postgres products",
  "description": "Catalog search",
  "tags": ["shop"],
  "desiredState": "running",
  "source": {
    "connectorId": "pg-main",
    "type": "postgres",
    "dsn": "postgres://cdc:secret@db:5432/shop",
    "database": "",
    "schema": "public",
    "table": "products",
    "slot": "",
    "publication": "",
    "serverId": 0,
    "region": "", "endpoint": "", "accessKeyId": "", "secretAccessKey": "",
    "brokers": [], "groupId": "",
    "driver": "", "cursorColumn": "", "pollInterval": "2s",
    "webhookSecret": ""
  },
  "mapping": {
    "primaryKey": ["id"],
    "primaryKeyField": "id",
    "keepKeyColumns": false,
    "include": [], "exclude": ["internal_notes"],
    "rename": {"customer_email": "email"},
    "softDeleteColumn": "deleted_at", "softDeleteValue": "",
    "geo": {"lat": "lat", "lng": "lng"},
    "timeFormat": "unix",
    "static": {"tenant": "acme"}
  },
  "index": {
    "uid": "products",
    "destinationId": "",
    "writeMode": "replace",
    "manageSettings": true,
    "settings": {"filterableAttributes": ["category"], "sortableAttributes": ["price"]}
  },
  "sync": {
    "mode": "snapshot_stream",
    "batchSize": 1000, "flushInterval": "1s", "maxInflight": 4,
    "retry": {"maxAttempts": 8, "initialBackoff": "200ms", "maxBackoff": "30s"},
    "safety": {
      "maxDeleteRatio": 0.2, "minDocsForDeleteGuard": 1000,
      "allowTruncate": false, "zeroDowntimeResync": true, "dumpBeforeResync": false
    }
  }
}
  • Use connectorId or the inline connection fields (type, dsn, …), not both: with a connector, the connector's connection fields are used and type is taken from it.
  • Durations are strings ("500ms", "2s"); a plain number is read as milliseconds.
  • desiredState is running, stopped or paused; the API sets it through actions, the tasks file through this field.
  • Responses add createdAt, updatedAt and a status object, and mask secrets as ••••••. Sending a masked value back keeps the stored secret.

Meili CDC user guide · last updated 2026-10-26 · Edit this page on GitHub

meilisearch-cdc is open source. Meilisearch is a trademark of Meili SAS; this project is not affiliated with it.