Features
MarkdownMeilisearch sync
Externa can optionally sync published collection items into Meilisearch for public site search. Admin SQL filters and AI embeddings stay unchanged.
Enable
MEILISEARCH_ENABLED=true
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_API_KEY=masterKey
MEILISEARCH_INDEX=externa
MEILISEARCH_INDEXABLE_STATUSES=published
When MEILISEARCH_ENABLED is false or MEILISEARCH_HOST is empty, sync is a no-op.
Run a queue worker (composer run dev locally, or php artisan queue:work / Horizon). Indexing is queued after published value writes.
Pre-1.2.0 indexes
If you enabled Meilisearch on a pre-1.2.0 build, the index may exist without a primary key and stay empty. Upgrade to 1.2.0, delete the externa index, and republish or resync items; Externa now creates it with primaryKey=id (#179).
What gets indexed
| Event | Behavior |
|---|---|
| Item create / update of published values | Upsert document |
Draft-only save (draft_data) | Not indexed |
| Item soft-delete / force-delete | Delete document |
| Item restore | Re-upsert published values |
| Bulk AI import | Suppressed (withoutMeilisearch); reindex later if needed |
If the item data bag has a status field, only values listed in MEILISEARCH_INDEXABLE_STATUSES stay indexed (default published). Missing status field → always eligible.
Document shape (single index, filter by collection_slug):
{
"id": "12",
"collection_id": 3,
"collection_slug": "posts",
"updated_at": "2026-10-02T12:00:00+00:00",
"searchable_text": "Hello world",
"title": "Hello world",
"status": "published"
}
Docker Compose
docker compose --profile meilisearch up --build
Service listens on host :7700. Inside the Compose network set:
MEILISEARCH_HOST=http://meilisearch:7700
MEILISEARCH_API_KEY=masterKey
Query from a headless site
Talk to Meilisearch directly from your Next.js (or other) server with a search-only API key — do not expose the master key in the browser.
// server-only example
const res = await fetch(
`${process.env.MEILISEARCH_HOST}/indexes/externa/search`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MEILISEARCH_SEARCH_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
q: 'hello',
filter: 'collection_slug = posts',
}),
},
)
See also Headless starter for the Public CMS API path; Meilisearch is an optional parallel index for full-text UX.
Out of scope
- Algolia
- Laravel Scout on EAV items
- CRDT / collaborative index merge
- Replacing admin / Public API
filter[...]SQL lists