Meilisearch search engine integration with the JavaScript/TypeScript SDK (v0.44+). Use when working with Meilisearch indexes, search queries, filters, facets, sorting, multi-search, tenant isolation, document sync, or search settings configuration. Triggers on: meilisearch client setup, index creation, addDocuments, search with filters, facetDistribution, tenant tokens, multi-search, ranking rules, filterable/sortable/searchable attributes. Use this skill any time the user mentions Meilisearch, search indexing, full-text search in their app, or asks about tenant-scoped search — even for simple tasks like 'add search to this page', because the skill contains critical multi-tenancy and filter syntax patterns that prevent data leaks.
94
94%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Highlight matching terms in results and crop long text fields to show relevant snippets.
const results = await index.search("trade show", {
attributesToHighlight: ["name", "description"],
highlightPreTag: "<mark>", // Default: <em>
highlightPostTag: "</mark>", // Default: </em>
attributesToCrop: ["description"],
cropLength: 50, // Characters around the match
cropMarker: "...", // Default: ...
});
// results.hits[0]._formatted.description
// → "...annual <mark>trade show</mark> with 200 exhibitors..."Controls how Meilisearch handles multi-word queries.
// "last" (default) — matches documents with ALL words first,
// then progressively drops words from the end
await index.search("portable trade show booth", {
matchingStrategy: "last",
});
// "all" — only returns documents matching ALL query words
await index.search("portable trade show booth", {
matchingStrategy: "all",
});
// "frequency" — removes the most common word first
await index.search("portable trade show booth", {
matchingStrategy: "frequency",
});Use "all" when precision matters (exact search). Use "last" (default) for broader recall.
Override which fields are searched without changing index settings:
// Only search the name field, ignoring description/tags
const results = await index.search("booth", {
attributesToSearchOn: ["name"],
});This is per-query — it doesn't change the index configuration.
Debug relevance by requesting the ranking score for each hit:
const results = await index.search("booth", {
showRankingScore: true,
showRankingScoreDetails: true,
});
// results.hits[0]._rankingScore → 0.89
// results.hits[0]._rankingScoreDetails → {
// words: { order: 0, matchingWords: 1, maxMatchingWords: 1, score: 1 },
// typo: { order: 1, typoCount: 0, maxTypoCount: 1, score: 1 },
// ...
// }Useful for understanding why certain results rank higher/lower than expected.
Search by geographic location. Documents must have _geo field with lat and lng.
// Add documents with geo data
await index.addDocuments([
{ id: "1", name: "McCormick Place", _geo: { lat: 41.8507, lng: -87.6153 } },
]);
// Configure _geo as filterable
await index.updateFilterableAttributes(["_geo", "tenant_id"]);
await index.updateSortableAttributes(["_geo"]);
// Search within radius (meters)
const results = await index.search("", {
filter: "_geoRadius(41.8507, -87.6153, 50000)", // 50km radius
sort: ["_geoPoint(41.8507, -87.6153):asc"], // Sort by distance
});
// Each hit gets _geoDistance (meters from the point)Return only one result per unique value of an attribute. Useful for deduplication.
await index.updateDistinctAttribute("product_group_id");
// Now search returns at most 1 hit per product_group_idOnly one distinct attribute can be set per index.