diff --git a/assets/images/cloud-search-rules/01-activate-addon.png b/assets/images/cloud-search-rules/01-activate-addon.png new file mode 100644 index 000000000..857b1bf63 Binary files /dev/null and b/assets/images/cloud-search-rules/01-activate-addon.png differ diff --git a/assets/images/cloud-search-rules/02-create-first-rule.png b/assets/images/cloud-search-rules/02-create-first-rule.png new file mode 100644 index 000000000..81729d99e Binary files /dev/null and b/assets/images/cloud-search-rules/02-create-first-rule.png differ diff --git a/assets/images/cloud-search-rules/03-manage-rule.png b/assets/images/cloud-search-rules/03-manage-rule.png new file mode 100644 index 000000000..0780131b3 Binary files /dev/null and b/assets/images/cloud-search-rules/03-manage-rule.png differ diff --git a/assets/images/cloud-search-rules/04-add-condition.png b/assets/images/cloud-search-rules/04-add-condition.png new file mode 100644 index 000000000..abc61250b Binary files /dev/null and b/assets/images/cloud-search-rules/04-add-condition.png differ diff --git a/assets/images/cloud-search-rules/05-add-pin.png b/assets/images/cloud-search-rules/05-add-pin.png new file mode 100644 index 000000000..31ad9bcd1 Binary files /dev/null and b/assets/images/cloud-search-rules/05-add-pin.png differ diff --git a/assets/images/cloud-search-rules/06-rules-list.png b/assets/images/cloud-search-rules/06-rules-list.png new file mode 100644 index 000000000..f2e756865 Binary files /dev/null and b/assets/images/cloud-search-rules/06-rules-list.png differ diff --git a/capabilities/overview.mdx b/capabilities/overview.mdx index 4a4d6d8f8..77a5b3867 100644 --- a/capabilities/overview.mdx +++ b/capabilities/overview.mdx @@ -1,7 +1,7 @@ --- title: Capabilities overview sidebarTitle: Overview -description: Explore all Meilisearch capabilities, from full-text and semantic search to filtering, analytics, and multi-tenancy. +description: Explore all Meilisearch capabilities, from full-text and semantic search to filtering, curation, analytics, and multi-tenancy. --- Meilisearch provides a comprehensive set of search and data management capabilities. Each capability is documented with an overview, getting started guide, how-to guides, and advanced topics. @@ -25,6 +25,9 @@ Meilisearch provides a comprehensive set of search and data management capabilit Narrow, order, and categorize results with filters, sort rules, and faceted navigation. + + Curate search results by pinning selected documents when query- or time-based conditions match. + Re-rank search results based on user context and behavior for tailored experiences. diff --git a/capabilities/search_rules/advanced/pinning_behavior.mdx b/capabilities/search_rules/advanced/pinning_behavior.mdx new file mode 100644 index 000000000..73acef133 --- /dev/null +++ b/capabilities/search_rules/advanced/pinning_behavior.mdx @@ -0,0 +1,52 @@ +--- +title: Search rule behavior +sidebarTitle: Pinning behavior +description: Learn how search rules interact with ranking, filters, precedence, and different search modes. +--- + +This page explains how search rules interact with ranking, filters, matching, and pagination. Read it before building workflows that depend on pinned results in production. + +## Pinning does not change ranking + +Pinning does not rewrite your ranking rules and does not assign scores to pinned documents. Meilisearch computes organic results first, then inserts the surviving pinned documents at their requested positions. Organic ranking still decides the order of every non-pinned hit. + +Pinning is the only supported action today. Boosting, demoting, and burying are planned for future releases. + +## Pinned documents do not need to match the query text + +A pinned document can appear even when it is not a lexical match for the query. If the rule matches, the document exists, and it passes filters, Meilisearch inserts it into the result list. + +This is useful for help centers, promotions, and landing pages, where the document you want to show is not always the best textual match. + +## Filters still apply + +Pinned documents do not bypass filters. If the current search filters exclude a pinned document, Meilisearch drops it instead of forcing it into the response. This keeps pinned results consistent with visibility, safety, and authorization rules already enforced by your filters. + +## Matching behavior of `contains` + +The query condition `contains` performs a case-insensitive literal substring match. It is not fuzzy, semantic, or pattern-aware. + +For example, `contains: "call history"` matches `where is call history`, but it does not match `history of calls` because the substring order differs. + +Search rules do not support regex, wildcards, or numeric-pattern matching (such as "any 6-digit code"). For more flexible matching, create several rules with different `contains` values. + +## Precedence between rules + +Use `priority` to control which rule wins when several matching rules compete. Lower numeric values take precedence over higher ones. + +If you omit `priority`, treat the rule as having the lowest precedence. + +## Result-set behavior + +Search rules preserve normal response behavior: + +- A document should not appear twice if it is both organic and pinned +- Pagination remains coherent +- Facet distribution stays consistent with the final surviving result set +- Search rules work in regular search, hybrid search, federated search, and network search + +## Storage model + +Search rules are stored at the instance level, not as per-index settings. A single rule can target documents in any index by setting `indexUid` inside the action selector. This keeps curation configuration centralized, but it also means that rules live outside the usual index-settings lifecycle. + +For the full list of matching and action capabilities the current version supports (and the ones it does not), see [the overview](/capabilities/search_rules/overview#current-scope). diff --git a/capabilities/search_rules/getting_started.mdx b/capabilities/search_rules/getting_started.mdx new file mode 100644 index 000000000..5e0b82e34 --- /dev/null +++ b/capabilities/search_rules/getting_started.mdx @@ -0,0 +1,142 @@ +--- +title: Getting started with search rules +sidebarTitle: Getting started +description: Enable search rules, create your first rule, and verify how pinned results appear in search. +--- + +This guide walks you through enabling the experimental flag, creating your first rule, and checking how it affects search results. You can do this from the Meilisearch Cloud dashboard or directly against the API. + +## Set up from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### Activate the feature + +Search rules are an add-on. On the Search rules tab, click **Enable Search Rules** to turn on the `dynamicSearchRules` experimental flag for the project. + +![Meilisearch Cloud Search rules tab with an "Enable Search Rules" button in the add-on panel](/assets/images/cloud-search-rules/01-activate-addon.png) + +### Create your first rule + +Once the feature is active, the Search rules tab shows a short explainer and a **New rule** button. Click it to start building a rule. + +![Search rules landing page with a "New rule" button in the top-right corner](/assets/images/cloud-search-rules/02-create-first-rule.png) + +### Fill in the rule's general information + +Give the rule a **Rule ID** (used as the `uid`), an optional **Description**, and a **Priority** if several rules might match the same query. Keep the **Active** toggle on to apply the rule at search time. + +![Rule editor showing Rule ID "summer-sales", a description, priority 0, and General / Conditions / Actions sections](/assets/images/cloud-search-rules/03-manage-rule.png) + +### Add conditions + +In the **Conditions** block, click **Add condition** to open the condition dialog. Pick a type (**Query contains**, **Query is empty**, or **Time window**) and fill in the value. Every condition you add is combined with `AND`, so the rule fires only when all conditions are true at the same time. + +![Add condition dialog with a "Query contains" type and a "Contains substring" input](/assets/images/cloud-search-rules/04-add-condition.png) + +### Pin documents to fixed positions + +In the **Actions** block, click **Add pin**. Choose the source **Index**, pick the **Document** to promote, and set its target **Position** in the result list. Repeat for each document you want to pin. When the rule is ready, click **Create rule**. + +![Add pin dialog with an index dropdown, a document selector, and a position input](/assets/images/cloud-search-rules/05-add-pin.png) + +### Review and manage your rules + +Back on the Search rules tab, every rule you have created is listed with its conditions, actions, priority, and active state. Use the row toggle to pause a rule without deleting it, or the icons on the right to edit or remove it. + +![Search rules list with one "summer-sales" rule showing its conditions, pin action, priority, and active toggle](/assets/images/cloud-search-rules/06-rules-list.png) + +## Set up from the API + +Prefer the API? The same flow maps directly to the `/dynamic-search-rules` routes. + +### Enable the experimental flag + +Send a `PATCH /experimental-features` to turn on the feature: + +```json +{ + "dynamicSearchRules": true +} +``` + +While the flag is disabled, the `/dynamic-search-rules` routes reject requests and saved rules do not apply at search time. + +### Create a rule + +Create a rule with `PATCH /dynamic-search-rules/invoice-help`: + +```json +{ + "description": "Promote billing help for invoice searches", + "active": true, + "conditions": [ + { "scope": "query", "contains": "invoice" } + ], + "actions": [ + { + "selector": { "indexUid": "support", "id": "billing-workspace-overview" }, + "action": { "type": "pin", "position": 0 } + } + ] +} +``` + +This rule pins the document `billing-workspace-overview` in the `support` index to the first position whenever a query contains the substring `invoice`. + +The route behaves as an upsert: + +- It returns `201 Created` when the rule does not exist yet +- It returns `200 OK` when you update an existing rule + +### Check the stored rule + +Retrieve the rule you just created with `GET /dynamic-search-rules/invoice-help`. The response should include the `uid`, conditions, and actions you sent: + +```json +{ + "uid": "invoice-help", + "description": "Promote billing help for invoice searches", + "active": true, + "conditions": [ + { "scope": "query", "contains": "invoice" } + ], + "actions": [ + { + "selector": { "indexUid": "support", "id": "billing-workspace-overview" }, + "action": { "type": "pin", "position": 0 } + } + ] +} +``` + +### Run a matching search + +Send a normal search request to the index, for example `POST /indexes/support/search`: + +```json +{ + "q": "invoice settings" +} +``` + +If `billing-workspace-overview` exists in the `support` index and survives the current filters, Meilisearch inserts it at position `0`. The rest of the response is the normal organic result set, with duplicates removed. + +Search rules do not replace organic results. Meilisearch still computes them, then inserts the pinned documents on top. + +## Next steps + + + + Promote a landing page or help article for a query + + + Learn how search rules interact with ranking and filters + + + Review endpoints and request fields + + + Learn more about experimental feature management + + diff --git a/capabilities/search_rules/how_to/curate_empty_query.mdx b/capabilities/search_rules/how_to/curate_empty_query.mdx new file mode 100644 index 000000000..844abf400 --- /dev/null +++ b/capabilities/search_rules/how_to/curate_empty_query.mdx @@ -0,0 +1,127 @@ +--- +title: Curate default results for an empty query +description: Use the isEmpty condition to control which documents appear when users open search without typing anything. +--- + +Many search experiences start with an empty state: a search bar that users click on before typing anything. By default, Meilisearch returns documents in their stored order, which rarely reflects what you want users to see first. The `isEmpty` query condition fires when the query string is empty or missing, so you can curate a welcome list of featured documents without changing any organic ranking. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +On the `support` help center, the search bar is visible on every page. When users focus it without typing anything, they currently see the five oldest documents, which are not the most helpful. You want to replace that default list with three curated articles that cover the most common starting questions: + +- `quickstart-overview` at position `0` +- `popular-integrations` at position `1` +- `contact-support` at position `2` + +A single rule with an `isEmpty` condition and three pin actions handles this. + +## Set up from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Create a new rule + +Click **New rule**. Give it a descriptive Rule ID such as `empty-state-help`, add a short description, and keep the **Active** toggle on. + +![Rule editor with a Rule ID and description filled in](/assets/images/cloud-search-rules/03-manage-rule.png) + +### 2. Add an empty query condition + +In the **Conditions** block, click **Add condition**. In the dialog: + +- Set **Type** to `Query is empty` + +`Query is empty` has no additional fields to fill. The condition simply asserts that the search request has no `q` value, or that `q` is an empty string. + +Click **Add condition** to save. + +![Add condition dialog with the "Query is empty" type selected](/assets/images/cloud-search-rules/04-add-condition.png) + +### 3. Pin the featured documents + +In the **Actions** block, click **Add pin** three times, once per document: + +- `support` / `quickstart-overview` at position `0` +- `support` / `popular-integrations` at position `1` +- `support` / `contact-support` at position `2` + +![Add pin dialog configured for the first featured document](/assets/images/cloud-search-rules/05-add-pin.png) + +### 4. Save the rule + +Click **Create rule**. The new rule appears in the Search rules list with the `isEmpty` condition and three pin actions. + +![Search rules list with the curated empty-state rule](/assets/images/cloud-search-rules/06-rules-list.png) + +## Set up from the API + +Send a `PATCH /dynamic-search-rules/empty-state-help`: + +```json +{ + "description": "Curate the default help-center browse state", + "active": true, + "conditions": [ + { "scope": "query", "isEmpty": true } + ], + "actions": [ + { + "selector": { "indexUid": "support", "id": "quickstart-overview" }, + "action": { "type": "pin", "position": 0 } + }, + { + "selector": { "indexUid": "support", "id": "popular-integrations" }, + "action": { "type": "pin", "position": 1 } + }, + { + "selector": { "indexUid": "support", "id": "contact-support" }, + "action": { "type": "pin", "position": 2 } + } + ] +} +``` + +- `isEmpty: true` means the rule only fires when the search request contains no query text. +- `isEmpty` and `contains` are mutually exclusive within a single condition. A condition either matches empty queries or matches queries containing a substring. +- Pinned documents do not need to match the query text, which is convenient here because there is no query text to match. + +## When the rule fires + +The rule fires whenever Meilisearch receives: + +- A search request with `q` missing from the body +- A search request where `q` is the empty string `""` +- A search request where `q` contains only whitespace (case-insensitive, accent-insensitive behavior is irrelevant here) + +The rule does not fire for requests with a non-empty query, even a single character. For those, use a `contains` condition instead. See [Pin one result for a query](/capabilities/search_rules/how_to/pin_one_result_for_query). + +## Variations and tips + +- **Pagination**: if your empty-state UI uses `limit` and `offset` to paginate, the pinned documents only appear on the first page. Users scrolling past position `2` see organic results as usual. +- **Facets and filters**: if users can filter the empty state (for example, by category), pins that do not match the active filters are dropped. The remaining pins stay at their positions, and organic results fill the gaps. +- **Seasonal overrides**: combine `isEmpty` with a time window condition to swap your default browse list during a campaign. See [Schedule a promotion for a limited time](/capabilities/search_rules/how_to/schedule_promotion). +- **Separate rules for separate indexes**: if you maintain multiple indexes with their own empty states (for example, `support` and `products`), create one rule per index. A single action's `indexUid` only controls one pin. + +## Next steps + + + + Pin a document for a specific query substring + + + Control multiple top positions at once + + + Limit a rule to a campaign window + + + Learn how pins interact with ranking, filters, and precedence + + + Full request and response shapes + + diff --git a/capabilities/search_rules/how_to/list_and_filter_rules.mdx b/capabilities/search_rules/how_to/list_and_filter_rules.mdx new file mode 100644 index 000000000..36b85737f --- /dev/null +++ b/capabilities/search_rules/how_to/list_and_filter_rules.mdx @@ -0,0 +1,103 @@ +--- +title: List or filter existing rules +description: Browse, paginate, and filter stored search rules to audit active campaigns or prepare bulk edits. +--- + +As your rule set grows, you will want to review what is active, find a specific rule quickly, or identify every rule that belongs to the same campaign before editing. The dashboard shows a table of every rule, and the API exposes the same view with pagination and attribute-based filters so you can script audits. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +You have roughly fifty search rules, and several of them belong to a running promotion cycle. Their Rule IDs all start with `promo-` (for example, `promo-summer-2026`, `promo-back-to-school-2026`). Before launching a new campaign, you want to list every rule with a `uid` starting with `promo-` so you can confirm which ones are still active. + +The Cloud dashboard lets you browse the list visually. The API lets you filter by `uid` pattern and by `active` status to narrow the results to exactly the rules you care about. + +## Browse rules from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Review the rules list + +The **Search rules** tab shows every stored rule in a single table. For each rule you can see: + +- **Rule ID** (the rule's `uid`) +- **Conditions** in a readable summary +- **Actions** (the number of pins and their positions) +- **Priority** +- **Active** toggle + +![Search rules list with several rules visible](/assets/images/cloud-search-rules/06-rules-list.png) + +The row toggle pauses a rule without deleting it. The icons on the right edit or remove a rule. + +### 2. Find a specific rule + +The dashboard list is designed for a moderate number of rules. If you need to search by `uid` pattern or by active state across many rules, use the API instead. The dashboard is good for spot checks, the API is good for audits. + +## List rules from the API + +Send a `POST /dynamic-search-rules` with pagination and optional filters: + +```json +{ + "offset": 0, + "limit": 20, + "filter": { + "attributePatterns": ["promo*"], + "active": true + } +} +``` + +The response contains the rules that match, with the same structure you used to create them (`uid`, `conditions`, `actions`, `priority`, `active`, `description`). + +### Parameters + +- `offset` and `limit` control pagination. Defaults are `offset: 0` and `limit: 20`. The maximum `limit` varies by Meilisearch version, keep requests under a few hundred rules per call. +- `filter.attributePatterns` accepts a list of attribute-pattern strings. `promo*` returns every rule whose `uid` starts with `promo`. `*seasonal*` returns every rule whose `uid` contains `seasonal`. Patterns are case-sensitive. +- `filter.active` filters by status: `true` returns only active rules, `false` returns only paused rules. Omit the field to return rules regardless of status. + +You can combine filters. The example above returns active rules whose `uid` starts with `promo-`, which is exactly the campaign-audit case described above. + +### Retrieve a single rule + +If you already know a rule's `uid`, send `GET /dynamic-search-rules/{uid}` instead: + +```http +GET /dynamic-search-rules/invoice-help +``` + +This returns the full rule definition, which is convenient when you want to edit or duplicate it. The same structure can be sent back to `PATCH /dynamic-search-rules/{uid}` after modification. + +## Common auditing patterns + +- **Expired promotions**: list every rule with `active: false` to find paused rules you might still want to keep for future campaigns, or delete if they are truly one-off. +- **Rules per index**: the list response includes each rule's actions, so you can group by `selector.indexUid` locally to see how many pins target each index. +- **Scheduled promotions**: list every rule whose conditions contain a `time` scope with a `start` in the future. This is a good sanity check before a campaign goes live. +- **Priority conflicts**: list rules that share a `contains` value and compare their `priority` values. If two rules have the same priority and target different documents for the same query, ordering is not guaranteed. + +## Variations and tips + +- **Pagination for large rule sets**: page through the rules in batches of 20 to 100. Do not try to fetch everything in a single request if you have hundreds of rules. +- **Scripting audits**: combine the list API with `DELETE /dynamic-search-rules/{uid}` or `PATCH /dynamic-search-rules/{uid}` to script bulk cleanup. Always list first, inspect the result, then apply changes. +- **Version control**: search rules live at the instance level, not inside an index settings object, so they are not exported with `meilisearch dumps`. Keep a copy of your important rules in your own version control if you need rollback safety. + +## Next steps + + + + Stop a rule without deleting it + + + Combine a query condition with a time window + + + Learn how pins interact with ranking, filters, and precedence + + + Full request and response shapes + + diff --git a/capabilities/search_rules/how_to/pause_a_rule.mdx b/capabilities/search_rules/how_to/pause_a_rule.mdx new file mode 100644 index 000000000..525cc53b1 --- /dev/null +++ b/capabilities/search_rules/how_to/pause_a_rule.mdx @@ -0,0 +1,98 @@ +--- +title: Pause a rule without deleting it +description: Temporarily stop a search rule from firing while keeping its configuration intact, so you can re-enable it later without rebuilding it. +--- + +Sometimes you need to stop a rule from applying without losing its configuration. A campaign ended early, a pinned document needs to be reviewed before going live again, or a rule is misbehaving in production and you want to disable it while you investigate. Flipping a rule to inactive stops it from firing at search time while preserving every condition, action, and metadata field, so you can reactivate it later in one click or one API call. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +The `summer-sale-2026` rule is promoting a campaign landing page for "summer sale" queries, but marketing asked you to pull the landing page off the homepage for a few hours while they fix a typo. You want the pin to stop applying immediately, but you do not want to delete the rule: you will reactivate it once the landing page is back online. + +The right move is to set `active` to `false`. The rule stays in storage with all its conditions and actions, it just does not fire until you flip it back on. + +## Pause a rule from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Locate the rule + +Find the rule in the Search rules list. Each row displays a status toggle on the right side, next to the edit and delete icons. + +![Search rules list showing the active toggle on each row](/assets/images/cloud-search-rules/06-rules-list.png) + +### 2. Flip the Active toggle + +Click the toggle to switch the rule from **Active** to **Inactive**. The change is saved immediately: the next search request no longer triggers this rule. + +When you are ready to reactivate the rule, flip the toggle back on. No other field is affected, so the rule fires exactly as it did before, with the same conditions and pin actions. + +## Pause a rule from the API + +Send a `PATCH /dynamic-search-rules/{uid}` with only the `active` field: + +```json +{ + "active": false +} +``` + +Because the `PATCH` route is an upsert that only modifies the fields you send, this request leaves every other field untouched. The response returns `200 OK` with the full updated rule, which should now show `"active": false`. + +To reactivate the rule, send the same request with `true`: + +```json +{ + "active": true +} +``` + +## What changes and what stays the same + +When a rule is paused: + +- It remains stored and visible in `GET /dynamic-search-rules/{uid}` and `POST /dynamic-search-rules` +- Its conditions, actions, priority, and description are unchanged +- It is skipped at search time, no pin is inserted +- Time conditions are evaluated normally if the rule is reactivated later. A rule reactivated after its `end` date still does not fire because the time condition fails + +When a rule is reactivated, it fires again from the next search request on, with no warm-up period. + +## Pause versus delete versus expire + +| Action | When to use | Reversible? | +|--------|-------------|-------------| +| Flip `active` to `false` | Short-term pause, known reactivation window, fast rollback | Yes | +| Remove the offending condition or action | Rule needs to change, not stop | Yes (by reverting) | +| `DELETE /dynamic-search-rules/{uid}` | Rule is permanently wrong or no longer relevant | No, you must recreate from scratch | +| Let a time window expire naturally | Campaign end is known in advance | No reactivation needed, no cleanup | + +Prefer pausing over deleting whenever you think the rule might come back. Deletion is final, and rebuilding a rule from memory is error-prone. + +## Variations and tips + +- **Safe rollback after a bad edit**: if you just made changes to a rule and something looks off in production, flip `active` to `false` first, then investigate. Do not delete the rule while debugging. +- **Seasonal rules on standby**: for recurring campaigns (Black Friday, summer sale), keep the rule paused between campaigns and reactivate it a few days before launch. Remember to update the time window before reactivating. +- **Bulk pause**: the API has no bulk update, so pausing many rules at once requires a script that iterates over rule IDs. See [List or filter existing rules](/capabilities/search_rules/how_to/list_and_filter_rules) for how to enumerate rules first. +- **Precedence interactions**: while a rule is paused, it does not participate in priority comparisons. A lower-priority rule that would normally lose to the paused rule can now fire. Double-check after pausing a high-priority rule. + +## Next steps + + + + Find rules to pause or reactivate + + + Expire a rule automatically with a time window + + + Learn how pins interact with ranking, filters, and precedence + + + Full request and response shapes + + diff --git a/capabilities/search_rules/how_to/pin_multiple_results.mdx b/capabilities/search_rules/how_to/pin_multiple_results.mdx new file mode 100644 index 000000000..4a5e3d8b5 --- /dev/null +++ b/capabilities/search_rules/how_to/pin_multiple_results.mdx @@ -0,0 +1,126 @@ +--- +title: Pin several results in a fixed order +description: Pin more than one document to specific positions for the same query by combining multiple pin actions in a single rule. +--- + +Sometimes a single pinned document is not enough. When a query covers a whole topic, you might want to control the first two, three, or more result slots so the most helpful documents always appear together, in a specific order. A single rule with several pin actions covers this: each action targets one document and one position, and Meilisearch respects the order you define. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +On the same `support` help center, users who type "invoice" also benefit from seeing the "Download monthly statements" guide (`id: "download-monthly-statements"`) right after the "Billing workspace overview" article. You want the two billing documents at positions `0` and `1`, with organic results filling the rest of the page. + +One rule with two pin actions is enough: no need to create two separate rules for the same query condition. + +## Set up from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Create or edit the rule + +If you have already created an `invoice-help` rule (see [Pin one result for a query](/capabilities/search_rules/how_to/pin_one_result_for_query)), click it in the list to edit it. Otherwise, click **New rule** and fill in the Rule ID, Description, and Priority as usual. + +![Rule editor with General, Conditions, and Actions sections](/assets/images/cloud-search-rules/03-manage-rule.png) + +### 2. Add the query condition + +In the **Conditions** block, add a **Query contains** condition with the substring `invoice`. You only need one condition: both pins fire together whenever the query matches. + +### 3. Add the first pin + +In the **Actions** block, click **Add pin**. Configure: + +- **Index**: `support` +- **Document**: `billing-workspace-overview` +- **Position**: `0` + +Click **Add pin** to save. + +![Add pin dialog with index, document, and position set for the first pin](/assets/images/cloud-search-rules/05-add-pin.png) + +### 4. Add the second pin + +Click **Add pin** again. Configure: + +- **Index**: `support` +- **Document**: `download-monthly-statements` +- **Position**: `1` + +Click **Add pin** to save. + +The **Actions** block now lists two pins. Meilisearch inserts them at positions `0` and `1` when the rule fires. + +### 5. Save the rule + +Click **Create rule** (or **Save changes** when editing). The rule appears in the Search rules list with the two pin actions visible in the row. + +![Search rules list showing the rule with two pin actions](/assets/images/cloud-search-rules/06-rules-list.png) + +## Set up from the API + +Send a `PATCH /dynamic-search-rules/invoice-help` with two actions: + +```json +{ + "description": "Show billing resources first for invoice searches", + "active": true, + "conditions": [ + { "scope": "query", "contains": "invoice" } + ], + "actions": [ + { + "selector": { "indexUid": "support", "id": "billing-workspace-overview" }, + "action": { "type": "pin", "position": 0 } + }, + { + "selector": { "indexUid": "support", "id": "download-monthly-statements" }, + "action": { "type": "pin", "position": 1 } + } + ] +} +``` + +- Positions are zero-indexed. `0` is the first slot, `1` is the second, and so on. +- Each action targets exactly one document. To pin three documents, add three actions. +- You can mix documents from different indexes by setting a different `indexUid` per action. This is useful for federated search layouts where the same rule should promote results across multiple indexes. + +## How Meilisearch evaluates the rule + +At search time, Meilisearch: + +1. Matches the search query against every active rule +2. For each matching rule, verifies that every pinned document exists and passes the current search filters +3. Inserts the surviving pinned documents at the positions you requested +4. Fills the remaining positions with organic results and removes duplicates + +If one of the pins is filtered out but the other one is not, only the surviving pin is inserted. Meilisearch does not shift the other pin to fill the gap: it stays at the position you configured, and organic results fill the vacated slot. + +## Variations and tips + +- **Non-contiguous positions**: nothing stops you from pinning at positions `0` and `5`. The gap between the two pins is filled with organic results. Use this when you want to interleave pinned and organic content. +- **Same document, same rule**: pinning the same document at two different positions is not useful. Meilisearch deduplicates the final result set, so the document only appears once. +- **Competing rules on the same position**: if two rules both try to pin a different document at position `0`, the rule with the lower `priority` value wins. See [Precedence between rules](/capabilities/search_rules/advanced/pinning_behavior#precedence-between-rules). +- **Large pinned lists**: pinning many documents removes relevance control over those slots. Keep pinned sets small, usually two to five documents, and let organic ranking handle the rest. + +## Next steps + + + + Start with the simplest pinning pattern + + + Learn how pins interact with ranking, filters, and precedence + + + Combine a query condition with a time window + + + Temporarily disable a rule without deleting it + + + Full request and response shapes + + diff --git a/capabilities/search_rules/how_to/pin_one_result_for_query.mdx b/capabilities/search_rules/how_to/pin_one_result_for_query.mdx new file mode 100644 index 000000000..b04edc272 --- /dev/null +++ b/capabilities/search_rules/how_to/pin_one_result_for_query.mdx @@ -0,0 +1,128 @@ +--- +title: Pin one result for a query +description: Pin a single document to a fixed position whenever a search query contains a specific substring. +--- + +Pinning one document for a known query is the most common search rule pattern. Use it when you know exactly which document users should see when they search for a particular term, such as pinning your "Billing workspace overview" article for "invoice" queries, or a password-reset guide for "password" queries. The pinned document appears on top of the organic results without changing how the rest of the results are ranked. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +You run a help center on a `support` index. Users often type "invoice" when they are looking for the "Billing workspace overview" article (document `id: "billing-workspace-overview"`). Currently, that article ranks third organically. You want it to appear first whenever the word "invoice" appears in the query. + +A single search rule with one condition and one pin action covers this. + +## Set up from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Create a new rule + +Click **New rule** in the top-right corner. + +![Search rules tab with the "New rule" button in the top-right corner](/assets/images/cloud-search-rules/02-create-first-rule.png) + +### 2. Fill in the rule's general information + +Give the rule a descriptive **Rule ID**, for example `invoice-help`. This value becomes the rule's `uid` in the API and cannot be changed later without recreating the rule. Add a short **Description** so your team knows what the rule does. Leave **Priority** at `0` unless you already have a competing rule, and keep the **Active** toggle on so the rule applies at search time. + +![Rule editor showing Rule ID, Description, and Priority fields with the Active toggle on](/assets/images/cloud-search-rules/03-manage-rule.png) + +### 3. Add a query condition + +In the **Conditions** block, click **Add condition**. In the dialog: + +- Set **Type** to `Query contains` +- Enter `invoice` in the **Contains substring** field + +Click **Add condition** to save. + +![Add condition dialog with "Query contains" type and a substring input](/assets/images/cloud-search-rules/04-add-condition.png) + +The match is case-insensitive and accent-insensitive, so the rule fires for "Invoice", "INVOICE", "invoice settings", and any other query that contains "invoice" as a substring. It does not fire for unrelated words that happen to share letters (for example, "voice"). + +### 4. Pin the target document + +In the **Actions** block, click **Add pin**. In the dialog: + +- Set **Index** to the index that holds the document, for example `support` +- Pick the document under **Document** (here, `billing-workspace-overview`) +- Set **Position** to `0` so the document lands in the first result slot + +Click **Add pin**. + +![Add pin dialog with index, document, and position configured](/assets/images/cloud-search-rules/05-add-pin.png) + +### 5. Save the rule + +Back on the rule editor, confirm that the Conditions and Actions sections show the entries you just created. Click **Create rule** in the bottom-right corner. + +The rule now appears in the Search rules list. You can edit it, deactivate it, or delete it from this view later. + +![Search rules list showing the new invoice-help rule](/assets/images/cloud-search-rules/06-rules-list.png) + +## Set up from the API + +Send a `PATCH /dynamic-search-rules/invoice-help` with the following body: + +```json +{ + "description": "Promote billing help for invoice searches", + "active": true, + "conditions": [ + { "scope": "query", "contains": "invoice" } + ], + "actions": [ + { + "selector": { "indexUid": "support", "id": "billing-workspace-overview" }, + "action": { "type": "pin", "position": 0 } + } + ] +} +``` + +- `uid` comes from the URL (`invoice-help`), not from the body. +- `position: 0` targets the first result slot. +- `indexUid` in the selector scopes the pin to the document living in the `support` index. If you omit it, Meilisearch treats the pin as a cross-index reference. +- The route is an upsert: it returns `201 Created` on the first write and `200 OK` on subsequent updates. + +## How Meilisearch evaluates the rule + +At search time, Meilisearch: + +1. Matches the search query against every active rule +2. For each matching rule, verifies that the pinned document exists and passes the current search filters +3. Inserts the surviving pinned document at the requested position +4. Removes duplicates from the final result set + +If the pinned document is missing from the index or filtered out by the search request, Meilisearch drops the pin instead of forcing the document into the response. Organic results fill the rest of the positions. + +## Variations and tips + +- **Shorter substrings for broader matches**: to fire on both "invoice" and "invoices", use `invoic` as the substring. +- **One substring per rule**: `contains` accepts a single substring. If you want the same pin for multiple unrelated queries, create a separate rule for each substring. +- **Cross-checking with filters**: if your search uses strict visibility filters, test the rule against a realistic query to confirm the pinned document is not filtered out. +- **Regex, wildcards, or fuzzy matching**: not supported. See [matching behavior of contains](/capabilities/search_rules/advanced/pinning_behavior#matching-behavior-of-contains). + +## Next steps + + + + Add multiple pin actions to the same rule + + + Combine a query condition with a time window + + + Learn how pins interact with ranking, filters, and precedence + + + Temporarily disable a rule without deleting it + + + Full request and response shapes + + diff --git a/capabilities/search_rules/how_to/schedule_promotion.mdx b/capabilities/search_rules/how_to/schedule_promotion.mdx new file mode 100644 index 000000000..d4424f9bc --- /dev/null +++ b/capabilities/search_rules/how_to/schedule_promotion.mdx @@ -0,0 +1,129 @@ +--- +title: Schedule a promotion for a limited time +description: Combine a query condition with a time window to run a campaign pin automatically and expire it without manual cleanup. +--- + +Campaigns rarely run forever. A summer sale landing page, a holiday banner, or a product launch should appear at the top of results for a precise window and then disappear without anyone needing to remember to delete the rule. Time conditions pair a query condition with a start and end timestamp, so the rule only applies inside that window. + + +Search rules are experimental. Enable the `dynamicSearchRules` flag with `PATCH /experimental-features` before creating rules. See [Getting started](/capabilities/search_rules/getting_started#enable-the-experimental-flag). + + +## Example scenario + +Your e-commerce team runs a summer sale from June 1 to June 30. During that window, whenever a user's query contains "summer sale", you want to pin the `summer-sale-landing-page` document (in the `products` index) at the top of the results. Outside that window, the rule must not fire at all, even if the query still contains "summer sale". + +One rule with two conditions (query + time) and one pin action covers this. + +## Set up from the Meilisearch Cloud dashboard + +Open your project in the [Meilisearch Cloud dashboard](https://cloud.meilisearch.com) and select the **Search rules** tab. + +### 1. Create a new rule + +Click **New rule**. Give it a Rule ID such as `summer-sale-2026`, add a description noting the campaign dates, and keep the **Active** toggle on. You can leave **Priority** at `0` unless another rule competes for the same query. + +![Rule editor with a summer sale rule being created](/assets/images/cloud-search-rules/03-manage-rule.png) + +### 2. Add the query condition + +In the **Conditions** block, click **Add condition** and pick **Query contains**. Enter `summer sale` as the substring. Click **Add condition** to save. + +Because `contains` is a case-insensitive literal substring match, the rule also fires for "Summer Sale", "SUMMER SALE discount", and "buy during the summer sale". It does not fire for "summer" or "sale" on their own. + +### 3. Add the time condition + +Click **Add condition** again and pick **Time window**. Enter: + +- **Start**: `2026-06-01T00:00:00Z` +- **End**: `2026-06-30T23:59:59Z` + +Timestamps are in UTC. Convert your campaign's local time window before entering them (for example, `2026-06-01T00:00:00-04:00` in New York becomes `2026-06-01T04:00:00Z` in UTC). + +Click **Add condition** to save. + +![Add condition dialog with the time window type and a start/end pair](/assets/images/cloud-search-rules/04-add-condition.png) + +The **Conditions** block now shows two entries. Both conditions are combined with `AND`, so the rule only fires when the query matches **and** the current time is inside the window. + +### 4. Pin the campaign page + +In the **Actions** block, click **Add pin**: + +- **Index**: `products` +- **Document**: `summer-sale-landing-page` +- **Position**: `0` + +Click **Add pin** to save. + +![Add pin dialog configured for the campaign landing page](/assets/images/cloud-search-rules/05-add-pin.png) + +### 5. Save the rule + +Click **Create rule**. The rule now appears in the Search rules list. Before June 1 and after June 30, the rule still exists but stays silent because the time condition never matches. + +![Search rules list with the summer sale rule](/assets/images/cloud-search-rules/06-rules-list.png) + +## Set up from the API + +Send a `PATCH /dynamic-search-rules/summer-sale-2026`: + +```json +{ + "description": "Promote the summer sale landing page during June 2026", + "active": true, + "conditions": [ + { "scope": "query", "contains": "summer sale" }, + { "scope": "time", "start": "2026-06-01T00:00:00Z", "end": "2026-06-30T23:59:59Z" } + ], + "actions": [ + { + "selector": { "indexUid": "products", "id": "summer-sale-landing-page" }, + "action": { "type": "pin", "position": 0 } + } + ] +} +``` + +- `scope: "time"` takes a `start` and an `end` timestamp in ISO 8601 UTC format. +- You can omit either `start` or `end` to create an open-ended window. For example, a rule with only `end` expires at that date but applies immediately. A rule with only `start` activates at that date and never expires until you remove it. +- Multiple conditions are combined with `AND`. You cannot express "this query between these dates OR that query between other dates" in a single rule, create two rules with different `uid` values instead. + +## How Meilisearch evaluates the rule + +On every search request, Meilisearch: + +1. Reads the current UTC time from the server +2. Checks each active rule. For a rule with a time condition, it verifies that the current time is between `start` and `end` +3. If any condition in the rule fails, the rule is skipped for this request +4. Otherwise, the rule fires and the pin is inserted + +The check happens at search time, not at rule creation time. There is no scheduler to configure and no background job to monitor: a rule that starts tomorrow simply stays silent today and starts firing tomorrow. + +## Variations and tips + +- **Always-on promotions with an expiry safety net**: if you want a rule to fire immediately but still auto-expire, set `start` in the past (or omit it) and `end` to the cutoff date. This is a good pattern for time-limited editorial pushes that you want to guarantee will disappear. +- **Overlapping campaigns**: if two rules target similar queries during overlapping windows, set different `priority` values so the winner is deterministic. Lower numbers win. +- **Timezones and daylight saving**: always convert to UTC. Relying on local time in the stored timestamps causes subtle bugs across daylight-saving transitions. +- **Deleting versus pausing versus expiring**: you can let the time window expire naturally (simplest), flip `active` to `false` to stop it early without deleting ([see Pause a rule](/capabilities/search_rules/how_to/pause_a_rule)), or `DELETE` the rule entirely if you never want to reuse it. +- **Auditing upcoming promotions**: use `POST /dynamic-search-rules` with a `uid` pattern filter to find every scheduled rule before a campaign starts. See [List or filter existing rules](/capabilities/search_rules/how_to/list_and_filter_rules). + +## Next steps + + + + Build a rule without a time window + + + Review the rules that will fire or expire soon + + + Stop a rule early without deleting it + + + Learn how pins interact with ranking, filters, and precedence + + + Full request and response shapes + + diff --git a/capabilities/search_rules/overview.mdx b/capabilities/search_rules/overview.mdx new file mode 100644 index 000000000..2a554114c --- /dev/null +++ b/capabilities/search_rules/overview.mdx @@ -0,0 +1,88 @@ +--- +title: Search rules +sidebarTitle: Overview +description: Pin selected documents at fixed positions in search results when query- or time-based conditions match. +--- + +Search rules pin selected documents at fixed positions in search results when specific conditions match. Pinning runs on top of organic search: Meilisearch still ranks results as usual, then inserts the pinned documents at the positions you asked for. + + +Search rules are experimental. Enable them with `PATCH /experimental-features` before using the API endpoints. + + + +The feature is exposed through the `/dynamic-search-rules` API routes and the `dynamicSearchRules` experimental flag. For readability, this documentation refers to it simply as "search rules" everywhere outside of API payloads. + +This is different from the [search rules object used in tenant tokens](/capabilities/security/advanced/tenant_token_payload#search-rules), which enforces filters. + + +## When to use search rules + +Search rules are a good fit whenever you know exactly which document should appear at which position for a specific query, empty state, or time window. For example, you might pin your billing help article to the top whenever users search for "invoice", feature a seasonal landing page for "summer sale" queries during a time-limited campaign, or curate a default list of onboarding articles for users who open search with an empty query. In each case, organic ranking still decides the rest of the result set. Only the pinned documents are promoted to fixed positions. + +For relevancy that adapts to every query, use [ranking rules](/capabilities/full_text_search/relevancy/ranking_rules) or [hybrid search](/capabilities/hybrid_search/overview) instead. + +## How search rules work + +```mermaid +flowchart LR + Q[Search query] --> O[Organic results] + Q --> R[Matching rules] + R --> P[Resolved pinned documents] + O --> M[Merge and deduplicate] + P --> M + M --> F[Final result set] +``` + +When a rule matches, Meilisearch: + +1. Computes the normal organic results +2. Resolves the pinned documents from the rule +3. Inserts those documents at the requested positions +4. Removes duplicates and returns the final result set + +Search rules do not change ranking or scoring. They insert pinned documents on top of the normal results. A pinned document can appear even if it does not match the query text, but filters still apply: if a pinned document does not satisfy the current filters, Meilisearch drops it. + +## Key concepts + +A rule combines three parts: + +- **Conditions** decide when the rule fires. Query conditions match the search string with `contains` (case-insensitive substring) or `isEmpty` (triggered by empty queries). Time conditions activate the rule during a window between a start and end timestamp. Multiple conditions combine with `AND`. +- **Actions** decide what the rule does. Pinning is the only available action today. An action targets one document via `indexUid` and `id`, and places it at a fixed `position` in the result list. +- **Priority** decides which rule wins when several match at once. Lower numeric values take precedence over higher ones. Omitting priority treats the rule as the lowest precedence. + +Rules also carry optional metadata such as `description` and an `active` flag that lets you pause a rule without deleting it. + +## Use cases + +- **Help centers and documentation**: Pin a specific answer when a user's query matches a known topic, so the most helpful article always appears first. +- **E-commerce merchandising**: Promote a campaign landing page or featured product during a sale window, then let the rule expire automatically when the campaign ends. +- **Editorial browse states**: Curate the default list users see when they open search with no query, highlighting starter content or featured collections. +- **Knowledge bases**: Surface operational runbooks or policy pages at the top when support-critical keywords appear, without rebuilding relevance rules. + +## Current scope + +Search rules apply to regular search, [hybrid search](/capabilities/hybrid_search/overview), [federated search](/capabilities/multi_search/overview), and network search. They do not support: + +- Regex, wildcard, or numeric-pattern matching +- Activation from filters, selected facets, locale, user context, or page context +- Actions other than pinning. Boosting, demoting, and burying are planned for future releases. + +See [pinning behavior](/capabilities/search_rules/advanced/pinning_behavior) for details on how rules interact with ranking, filters, and precedence. If your use case needs something search rules do not cover yet, [book a call with the Meilisearch team](https://meet.meilisearch.com/meetings/cloud/presentation) to share your requirements. + +## Next steps + + + + Enable the feature and create your first rule + + + Start with the most common pinning pattern + + + Matching behavior, precedence, and response details + + + Endpoints, rule fields, and update behavior + + diff --git a/config/navigation.json b/config/navigation.json index 0cae8c8a9..6e946b464 100644 --- a/config/navigation.json +++ b/config/navigation.json @@ -327,6 +327,30 @@ } ] }, + { + "group": "Search rules", + "pages": [ + "capabilities/search_rules/overview", + "capabilities/search_rules/getting_started", + { + "group": "How to", + "pages": [ + "capabilities/search_rules/how_to/pin_one_result_for_query", + "capabilities/search_rules/how_to/pin_multiple_results", + "capabilities/search_rules/how_to/curate_empty_query", + "capabilities/search_rules/how_to/schedule_promotion", + "capabilities/search_rules/how_to/list_and_filter_rules", + "capabilities/search_rules/how_to/pause_a_rule" + ] + }, + { + "group": "Advanced", + "pages": [ + "capabilities/search_rules/advanced/pinning_behavior" + ] + } + ] + }, { "group": "Teams", "pages": [ diff --git a/resources/help/experimental_features_overview.mdx b/resources/help/experimental_features_overview.mdx index ceba09a9a..dedd99b99 100644 --- a/resources/help/experimental_features_overview.mdx +++ b/resources/help/experimental_features_overview.mdx @@ -67,3 +67,4 @@ Activating or deactivating experimental features this way does not require you t | [Disable new indexer](/resources/self_hosting/configuration/overview) | Use previous settings indexer | CLI flag or environment variable | | [Allowed IP networks](/resources/self_hosting/configuration/overview) | Override default IP policy with allowed CIDR ranges | CLI flag or environment variable | | [Search personalization](/capabilities/personalization/getting_started/personalized_search) | Enables search personalization | CLI flag or environment variable | +| [Search rules](/capabilities/search_rules/overview) | Curate search results by pinning selected documents when query- or time-based conditions match | API route |