Headless CMS Integrations
How AACsearch integrates with headless CMS platforms — Strapi, Sanity, Contentful, Directus and Payload — and when to choose a plugin vs an integration guide.
Headless CMS platforms expose content via API rather than rendering pages. AACsearch supports them through two patterns:
- Plugin — code that runs inside the CMS process, listens to lifecycle hooks, and pushes deltas through the AACsearch Connector API. Strapi and (planned) Directus / Payload fit this pattern.
- Integration guide — no plugin; the CMS already publishes webhooks or change streams that you wire to AACsearch via a small adapter. Contentful and Sanity fit this pattern.
This page is the umbrella. Each platform has its own dedicated guide linked below.
Pattern matrix
| Platform | Pattern | Status | Dedicated page |
|---|---|---|---|
| Strapi 5.x | npm plugin | Beta | Strapi |
| Sanity.io | GROQ + listener | Integration guide | Sanity |
| Contentful | Webhook adapter | Integration guide | Contentful |
| Directus 10+ | npm extension | Planned | — |
| Payload CMS 2+ | Plugin (collections) | Planned | — |
| Generic / other | Custom connector | — | Custom Connector |
When to use a plugin
Pick the plugin path when the CMS has a stable extension API and runs as a long-lived process. The plugin gives you:
- Real-time hooks on every create / update / delete with the entity payload in scope, so there is no second API round trip to fetch the saved entity.
- An admin settings panel embedded in the CMS UI for the merchant or editor to manage the connector token.
- Localized content via the platform's native locale primitives, mapped into the AACsearch
localefield.
The Strapi plugin under packages/strapi-plugin/ is the reference implementation. It uses Strapi's lifecycles API (afterCreate, afterUpdate, afterDelete) to call enqueueDelta, plus a bootstrap hook to run an opt-in full sync at startup. The plugin source is reusable as a template for Directus and Payload — same lifecycle pattern, different host APIs.
When to use an integration guide
Pick the integration guide path when:
- The CMS is SaaS-hosted and you cannot run plugin code inside it (Contentful, Sanity).
- The CMS exposes a robust webhook or change-stream API that already includes the entity payload.
- You only need to index a subset of content types and adding a plugin is over-engineering for the use case.
For these platforms, the AACsearch docs describe how to:
- Subscribe to the CMS's webhook or change feed.
- Configure a small adapter (a serverless function, a webhook receiver in your existing API, or a Zapier zap) that maps the payload to
SyncProductInput. - Forward the delta to
POST /api/projects/:projectId/sync/delta.
See Contentful and Sanity for the working recipes.
Choosing your content model
Headless CMS content rarely fits the e-commerce product shape. The shared document schema accepts any field structure under attributes, so the mapping question becomes:
- Searchable fields →
title,description. Always send plain text after stripping markdown or rich-text JSON. - Faceted filters →
attributes.<key>with the value as the appropriate primitive type. AACsearch infers the facet type from the first batch. - Display fields →
attributes.<key>for anything you only need on the result card, not in the search query. - URLs and images →
product_url,image_url. The widget renders these by default. - Locale → the CMS's native locale, narrowed to
en | de | es | fr | ru(AACsearch supports those five). If your content is in another language, send the closest neighbor and store the actual code inattributes.language.
For non-product entities (articles, recipes, FAQ entries), reuse the same schema:
{
"external_id": "article-2451",
"title": "How to brew a pour-over",
"description": "A short guide…",
"categories": ["Guides", "Coffee"],
"tags": ["beginner", "pour-over"],
"image_url": "https://cms.example.com/img/pour-over.jpg",
"product_url": "https://site.example.com/guides/pour-over",
"locale": "en",
"attributes": {
"type": "article",
"author": "Jane Doe",
"published_at": "2025-01-12",
"read_time_minutes": 4
}
}Use attributes.type to distinguish content types in a single index, or create separate indexes per type if the merchant wants independent ranking and analytics.
Multi-locale strategy
Headless CMS platforms typically model locales explicitly. The AACsearch convention is one document per locale per entity:
external_idis suffixed by locale:article-2451:en,article-2451:de.- The widget passes
localefrom the storefront so users only see results in their language. - Updates to one locale do not affect documents for other locales — your event handler should emit only the locale that changed.
See Strapi for the canonical implementation of this pattern.