Why orchestrate the sync
Your catalog changes constantly: prices, stock, new products, seasonal retirements. A search index that drifts from the catalog erodes trust fast. Kestra makes the sync an observable, scheduled, retryable workflow, and handles Shopify’s pagination and rate limits for you so you’re not hand-rolling a resilient API client.Prerequisites
A running Kestra with the Meilisearch and Shopify plugins, plus a Meilisearch Cloud project and a Shopify Admin API access token (create a custom app in your Shopify admin under API credentials, and grant itread_products). You’ll need your store domain (your-store.myshopify.com) and the token (shpat_…).
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). Keep both the Meilisearch key and the Shopify token in Kestra secrets.Step 1: Backfill the catalog
The Shopify plugin’sproducts.List task fetches your products and, with fetchType: STORE, writes them to an ION file in internal storage, the format DocumentAdd consumes. It paginates and respects Shopify’s rate limits for you.
id as the primary key, but you’ll usually want to trim them to what search needs. Drop a JSONata TransformItems step between extract and index to shape clean documents:
Step 2: Incremental sync (the real use case)
Re-pulling the whole catalog every few minutes is wasteful and will bump into rate limits. Shopify’s API supports “give me what changed since X” natively, and the plugin exposes it asupdatedAtMin, so incremental sync is clean. On a schedule, ask only for products updated since the run’s scheduled time:
DocumentAdd is add-or-replace, so a product returned in two consecutive windows is just overwritten by its id. For a precise, gap-free watermark, store the last-synced timestamp in Kestra’s KV store (io.kestra.plugin.core.kv.Get / Set) and pass it to updatedAtMin instead of the schedule time.
Handling deletes
Deletes are the gap in any poll-based approach: a deleted or unpublished product simply stops appearing inproducts.List, so polling can’t see it go. Two options:
- Filter on publication and reconcile. Sync only
status: ACTIVE/ published products, and periodically re-index a full pull into a new Meilisearch index, then repoint an alias, so anything no longer active drops out. - Use Shopify webhooks (best for real-time). Configure a
products/delete(andproducts/update) webhook in Shopify pointing at a Kestra webhook trigger. The delete event carries the product id. Route it to Meilisearch’sdocuments/delete-batchendpoint via anhttp.Requesttask (the delete pattern is shown in full in Connect PostgreSQL to Meilisearch with Kestra). Webhooks also give you near-instant updates without polling.
Going to production
- Rate limits are handled by the plugin’s
rateLimitDelay, but keep the schedule sensible (every 15 minutes is a good default) and prefer webhooks for freshness. - Sync more than products. The plugin also lists orders and customers, so index those into separate Meilisearch indexes if you want to search them (for example, an internal order-lookup tool).
- Configure Meilisearch settings first. Set searchable attributes (title, vendor, tags), filterable attributes (product_type, price), and sortable attributes before the backfill so results rank well from the first pass.
- Backfill once, then sync. Seed with the Step 1 flow, then let the schedule (or webhooks) take over. Idempotent upserts make a re-run harmless.
Wrap-up
Shopify to Meilisearch is a backfill flow plus a scheduled incremental sync that leans on Shopify’s nativeupdatedAtMin filter, with webhooks as the upgrade path for real-time updates and deletes. Kestra handles pagination, rate limits, scheduling, and retries, so a custom, typo-tolerant storefront search stays in step with your catalog.