Skip to main content
If your services already talk over RabbitMQ, your data changes are already a stream of messages: product updates, inventory changes, new content. Turning that stream into a live Meilisearch index normally means writing and operating yet another consumer service. With Kestra it’s a trigger and a couple of tasks, in declarative YAML. This guide builds it in two stages: a batch consume-and-index flow to see the pieces, then a real-time trigger that indexes each message as it arrives. Because a queue consumer acknowledges messages as it goes, this is incremental by construction, so there’s no separate “first load versus incremental” to manage, just the stream. (This uses the AMQP 0-9-1 protocol, so it works with RabbitMQ and other AMQP brokers.)

Why orchestrate the sync

A hand-written RabbitMQ to Meilisearch consumer means managing connections, acknowledgements, deserialization, batching, retries, restarts, and monitoring, a service to keep alive. Kestra collapses that into a trigger plus tasks, with acknowledgement, retries, and observability handled for you.

Prerequisites

A running Kestra with three plugins (Meilisearch, AMQP, and the transform plugin for reshaping messages), plus a Meilisearch Cloud project and a RabbitMQ broker:
Get your Cloud credentials. In the Meilisearch Cloud dashboard, create a project and copy its Project URL (the url in the flows below) and its Default Admin API Key (Settings, then API Keys). Store the key as the MEILISEARCH_API_KEY Kestra secret, and keep broker credentials in secrets too.
The events are JSON messages describing products, for example { "id": "prod-1", "name": "Mechanical Keyboard", "stock": 42 }.

Understanding the shape: a batch consume-and-index flow

Before wiring up real-time, here’s a flow you can run on demand. It declares a queue, publishes a few test messages, consumes them, reshapes them, and indexes them:
The one non-obvious step is extract_payload. The Consume task writes a full AMQP envelope per message: data (the body), plus headers, contentType, messageId, timestamp, and so on. You don’t want all that in your search index, just the body, so a JSONata TransformItems with expression: data plucks the payload out of each record. (In this demo create_queue and publish set the stage. In production you’d delete them, since your services already produce to the queue.)

The real deal: real-time indexing

Kestra’s AMQP RealtimeTrigger holds a persistent consumer open and starts one execution per message the instant it’s delivered. A published event is searchable in Meilisearch within seconds, with no cron and no polling.
Here the message body is available directly as {{ trigger.data }}. The flow writes it to internal storage as an ION document and indexes it. Publish a message to live-products and it shows up in search seconds later. Because DocumentAdd is add-or-replace, this is automatically an upsert stream: publish an updated event for prod-1 and it overwrites the existing document by primary key, exactly right when a queue carries a changelog of your entities.

Handling deletes

Represent deletions as messages and act on them. Publish an explicit delete event, for example { "id": "prod-1", "op": "delete" }, and branch on it inside the per-message execution: route delete events to Meilisearch’s documents/delete-batch endpoint and everything else to DocumentAdd. An If task reading trigger.data does the routing:
The delete branch sends a one-element array (["prod-1"]) to documents/delete-batch. For the database-side soft-delete pattern (query recently-deleted rows, then delete-batch), see Connect PostgreSQL to Meilisearch with Kestra.

Going to production

  • Drop the producer tasks. create_queue and publish are only there to generate test data. In production your services produce to the queue and Kestra only consumes.
  • Real-time versus batch. The RealtimeTrigger gives second-scale freshness, one execution per message. For very high volume, the batch Consume pattern with a larger maxRecords on a schedule is more efficient. Choose freshness or throughput.
  • Acknowledgement. By default the consumer acks messages as it processes them, so a restart resumes from the queue without reprocessing. Your incrementality comes for free from the broker.
  • Retries. Add a retry block so a transient Meilisearch error re-attempts the message rather than dropping it. Pair with a dead-letter queue on the broker for poison messages.

Wrap-up

Kestra turns “keep Meilisearch in sync with a RabbitMQ queue” into a trigger and a couple of tasks. The RealtimeTrigger gives you live indexing with acknowledgement handled for you. Add-or-replace semantics make the stream an idempotent upsert feed, and deletes are just another message type. Publish events as you already do, and Kestra keeps search live.