Meilisearch Patterns
Quick Guide: Use
meilisearch(v0.56+) as the TypeScript client for Meilisearch v1.x. All write operations (document adds, setting changes, index creation) are asynchronous -- they return anEnqueuedTaskPromiseand are processed in a background queue. You MUST configurefilterableAttributesandsortableAttributeson the index before using filter/sort in search queries -- this triggers a full re-index. Useclient.index("name")for a lazy reference (no network call) vsclient.getIndex("name")which fetches from server. Use.waitTask()onEnqueuedTaskPromiseonly in scripts/seeds/tests -- never in request handlers.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST configure filterableAttributes on the index BEFORE using filter in search queries -- filters silently return no results if the attribute is not in filterableAttributes)
(You MUST configure sortableAttributes on the index BEFORE using sort in search queries -- sort on unconfigured attributes is silently ignored)
(You MUST NOT call .waitTask() in production request handlers -- it blocks the event loop polling Meilisearch until the task completes; use it only in scripts, seeds, and tests)
(You MUST set the primary key explicitly when documents lack an id field -- Meilisearch auto-infers primary key only on first document add, and wrong inference causes indexing failures on subsequent batches)
</critical_requirements>
Examples
- Core Patterns -- Client setup, document operations, search basics, task management, TypeScript integration
- Filtering & Facets -- Filter syntax, faceted search, geo search, sortable attributes
- Index Settings -- Ranking rules, typo tolerance, synonyms, stop words, searchable attributes, pagination
- Security & Multi-Tenancy -- API keys, tenant tokens, search rules, multi-tenant patterns
Additional resources:
- reference.md -- Search parameter cheat sheet, settings defaults, decision frameworks, anti-patterns
Auto-detection: Meilisearch, meilisearch, MeiliSearch, meilisearch-js, client.index, addDocuments, updateDocuments, multiSearch, filterableAttributes, sortableAttributes, searchableAttributes, rankingRules, typoTolerance, tenant token, generateTenantToken, EnqueuedTaskPromise, waitTask, facets, _geoRadius, _geoBoundingBox, _geoPoint, instantsearch
When to use:
- Adding full-text search to an application (product search, content search, autocomplete)
- Implementing faceted navigation (category filters, price ranges, attribute counts)
- Building geo-aware search (find nearby, sort by distance)
- Multi-tenant search where tenants share an index but see only their documents
- Search across multiple indexes simultaneously (multi-search, federated search)
- Real-time document indexing with typo-tolerant instant search
Key patterns covered:
- Client initialization and connection management
- Document CRUD operations with async task handling
- Search with filtering, sorting, facets, and highlighting
- Geo search with
_geoRadius,_geoBoundingBox, and distance sorting - Multi-search and federated search across indexes
- Index settings configuration (ranking rules, typo tolerance, synonyms, stop words)
- Tenant tokens for multi-tenant access control
- TypeScript generics for typed search results
When NOT to use:
- Full-text search on a relational database (use your database's built-in full-text search for simple cases)
- Log aggregation or analytics queries (use a dedicated log/analytics search engine)
- Vector-only semantic search without keyword component (use a dedicated vector database)
- Searching fewer than ~1,000 documents (client-side filtering is simpler)
<philosophy>
Philosophy
Meilisearch is a search engine, not a database. It indexes documents for fast retrieval but is not the source of truth. The core principles:
- Async everything -- All write operations (documents, settings, index management) are queued and processed asynchronously. The API returns a task ID immediately. Design your application to not depend on instant indexing.
- Configure before search -- Filterable attributes, sortable attributes, and searchable attributes must be configured BEFORE they can be used in search queries. This triggers a re-index of all documents.
- Typo tolerance by default -- Meilisearch handles typos out of the box. Tune
typoTolerancesettings to disable it for specific fields (product codes, serial numbers) rather than trying to implement exact matching manually. - Primary key matters -- Every document needs a unique primary key. Meilisearch auto-infers it from the first document, but explicit is better than implicit. Set it on index creation.
- Search, don't query -- Meilisearch is optimized for human search queries (typo-tolerant, prefix matching, ranking). It is not a SQL replacement. Use filters for structured queries, search for natural language.
<patterns>
Core Patterns
Pattern 1: Client Setup
Initialize the client with host and API key. Use client.index() for a lazy local reference (no network call) -- prefer this over client.getIndex() which hits the server.
// Good Example -- Typed client setup
import { Meilisearch } from "meilisearch";
function createSearchClient(): Meilisearch {
const host = process.env.MEILISEARCH_URL;
const apiKey = process.env.MEILISEARCH_API_KEY;
if (!host) {
throw new Error("MEILISEARCH_URL environment variable is required");
}
return new Meilisearch({ host, apiKey });
}
export { createSearchClient };
Why good: Environment variable validation, named export, apiKey is optional (Meilisearch allows unauthenticated access in development)
// Bad Example -- Hardcoded credentials
import { Meilisearch } from "meilisearch";
const client = new Meilisearch({
host: "http://localhost:7700",
apiKey: "masterKey123",
});
Why bad: Hardcoded host and API key leak in version control, master key exposed (use scoped API keys in production)
See examples/core.md for health checks, AbortController usage, and custom request configuration.
Pattern 2: Document Indexing (Async)
All document operations return EnqueuedTaskPromise. The documents are NOT searchable immediately -- they enter a task queue.
// Good Example -- Add documents with explicit primary key
interface Product {
productId: string;
name: string;
description: string;
price: number;
categories: string[];
}
const index = client.index<Product>("products");
// First add: set primary key explicitly
const task = await index.addDocuments(products, { primaryKey: "productId" });
// task.taskUid: number -- use this to track progress
Why good: Explicit primary key prevents auto-inference issues, TypeScript generic provides type safety on document shape
// Bad Example -- Relying on auto-inference
const index = client.index("products");
await index.addDocuments(products); // No primary key specified
// If first document has both 'id' and 'productId', Meilisearch guesses wrong
Why bad: Meilisearch infers primary key from the first document -- if it guesses wrong, all subsequent adds may fail with primary key conflicts
See examples/core.md for update, delete, batching, and task management patterns.
Pattern 3: Search with Filters
Filters require filterableAttributes to be configured first. Filter syntax uses SQL-like operators with AND/OR/NOT.
// Good Example -- Search with filter and sort
const MIN_PRICE = 10;
const MAX_PRICE