Features

Markdown

Meilisearch 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

EventBehavior
Item create / update of published valuesUpsert document
Draft-only save (draft_data)Not indexed
Item soft-delete / force-deleteDelete document
Item restoreRe-upsert published values
Bulk AI importSuppressed (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