Using Search Rules
Shoppers searching for drill should not always see the same ranking as every other query. Search rules let you boost or bury products, pin or hide specific products, rewrite a query, or add a filter when a shopper query — or a browse listing — matches a trigger you define.
Search rules live in rule groups. Each group is a named campaign container. You attach one or more groups to a search profile with the ordered rule_groups attribute. Until a group is attached, its rules have no effect on search.
Search rules are evaluated when a search runs, not when products are indexed. Creating or updating rules does not require a catalog reindex. Conditions and filters must still reference indexed fields — list indexed fields to see what is available, and enable custom attributes through indexable fields first if needed.
Always-On Boosts vs Query-Triggered Rules
Catalog Search supports two related merchandising tools on a search profile:
| Tool | When it applies | Configure with |
|---|---|---|
| Always-on boost / bury | Every search that uses the profile | Profile boosts |
| Search rules | Only when a rule trigger matches the shopper query or browse | Rule groups attached via profile rule_groups |
Use always-on boosts for storewide policies (for example, always promote in-stock featured products). Use search rules for query- or browse-specific campaigns (for example, boost Milwaukee when the shopper searches for drill).
Prefer one clear strategy for a given outcome. When both apply, verify ranking with the same profile, query, filters, and shopper sort your storefront will use.
How Search Rules Work
- Create a rule group with a unique
slug. - Add rules to the group. Each rule has a trigger, one or more actions, an
execution_order, and an optionalstop_after_matchflag. - Attach the group UUID to a search profile's
rule_groupsarray. Earlier entries in the array run first across groups. - Search with that profile. Matching rules apply their actions; unmatched queries keep the profile's normal ranking.
Rules are evaluated in one profile-wide pipeline: profile rule_groups order, then each rule's execution_order (lower runs first).
A non-empty rule_groups array turns on query-time rules for every search that uses that profile. Rule actions still apply only when a trigger matches. Set rule_groups to [] to return to profile-only behavior.
Triggers
Each rule has a trigger that decides when the rule matches.
match | When the rule runs | query |
|---|---|---|
exact | Normalized shopper query equals the trigger text | Required |
contains | Trigger text appears as a substring in the normalized query | Required |
browse | Non-autocomplete browse search (q=*), such as category or “view all” pages | Omit |
Query text is normalized before matching: trimmed, lowercased, and with whitespace collapsed.
| Trigger | Example | Matches | Does not match |
|---|---|---|---|
Exact drill | match: exact, query: drill | drill, Drill | cordless drill |
Contains drill | match: contains, query: drill | drill, cordless drill driver | hammer |
| Browse | match: browse | q=* browse listings | Typed search queries, autocomplete |
Actions
A rule must include at least one action.
Boost and bury
Boost and bury use the same condition syntax as profile boosts. Positive weights promote matching products; negative weights bury them; 0 is a neutral exception (useful to exempt a narrower set from a broader bury).
{
"boosts": [
{ "condition": "extensions.products(Details).is_featured:=true", "weight": 100 },
{ "condition": "extensions.products(Details).is_clearance:=true", "weight": -100 }
]
}
When several matching rules set overlapping boost/bury conditions:
- Distinct conditions are merged.
- For the same condition, the earlier rule wins.
- Bury overrides boost when they conflict.
- Stronger bury wins over weaker bury.
- A narrower weight of
0can exempt products from a broader bury.
Shopper-selected sort remains the primary ordering. Rule boost/bury ranks otherwise comparable results. If a shopper sort already uses the maximum number of sort fields supported by the platform, rule boost/bury may not affect ranking — test important campaigns with the storefront sorts shoppers actually use.
Pin and hide
Pins place products at one-based global result positions. Active filters still apply, so a pinned product that fails the current filters does not appear. Hides remove products from results. If the same product is both pinned and hidden, hide wins.
{
"pins": [
{ "product_id": "978dfdd0-7cbf-454d-90b4-3d61de60ca46", "position": 1 }
],
"hides": [
{ "product_id": "15b0b18e-48bb-47c8-96e8-3d5739e68dfd" }
]
}
Query rewrite
Replace the shopper query when the rule matches. The first rewrite wins — if several matching rules set replace_query, only the earliest rewrite in the profile-wide pipeline is applied. Rule matching then runs once more against that replacement text. Use this for common typos or alternate terms that should resolve to a canonical query.
{
"replace_query": "drill"
}
Rule filter
Add an indexed-field filter only when the trigger matches. The expression is ANDed with profile filters and any shopper filters on the request.
{
"filter_by": "extensions.products(Details).is_in_stock:=true"
}
Ordering and stop_after_match
| Control | Scope | Behavior |
|---|---|---|
rule_groups array position | Across attached groups | Earlier UUID runs first |
execution_order | Within a group | Lower positive integer runs first; must be unique per group |
stop_after_match | Profile-wide pipeline | When true and the rule matches, later rules (including later groups) are skipped |
Use stop_after_match when a specific campaign must take exclusive precedence over broader rules that would otherwise also match.
Quick Reference
Rule groups and rules are managed through the following endpoints, relative to your API base URL (for example https://useast.api.elasticpath.com/v2):
| Operation | Method & Path |
|---|---|
| Create a rule group | POST /pcm/catalogs/search/rule-groups |
| List rule groups | GET /pcm/catalogs/search/rule-groups |
| Get a rule group | GET /pcm/catalogs/search/rule-groups/{rule_group_id} |
| Update a rule group | PUT /pcm/catalogs/search/rule-groups/{rule_group_id} |
| Delete a rule group | DELETE /pcm/catalogs/search/rule-groups/{rule_group_id} |
| Create a rule | POST /pcm/catalogs/search/rule-groups/{rule_group_id}/rules |
| List rules | GET /pcm/catalogs/search/rule-groups/{rule_group_id}/rules |
| Get a rule | GET /pcm/catalogs/search/rule-groups/{rule_group_id}/rules/{rule_id} |
| Update a rule | PUT /pcm/catalogs/search/rule-groups/{rule_group_id}/rules/{rule_id} |
| Delete a rule | DELETE /pcm/catalogs/search/rule-groups/{rule_group_id}/rules/{rule_id} |
| Move a rule | POST /pcm/catalogs/search/rule-groups/{rule_group_id}/rules/{rule_id}/move |
A rule group has these attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Unique identifier within the tenant. Pattern: ^[a-zA-Z0-9_.-]+$. |
description | string | No | Human-readable summary of the group's intent. |
A search rule has these attributes:
| Attribute | Type | Required | Description |
|---|---|---|---|
slug | string | No | Human-readable identifier within the group. |
description | string | No | Summary of what the rule does. |
execution_order | integer | Yes | Lower values run first within the group. Must be unique and at least 1. |
trigger | object | Yes | When the rule matches. See Triggers. |
actions | object | Yes | At least one of boosts, pins, hides, replace_query, or filter_by. |
stop_after_match | boolean | No | When true, skip later matching rules after this rule matches. Default false. |
JSON:API types are catalog_search_rule_group and catalog_search_rule.
Creating a Rule Group
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule_group",
"attributes": {
"slug": "seasonal-drill-campaign",
"description": "Merchandising rules for drill searches"
}
}
}'
Example response:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "catalog_search_rule_group",
"attributes": {
"slug": "seasonal-drill-campaign",
"description": "Merchandising rules for drill searches"
},
"meta": {
"owner": "store",
"created_at": "2026-03-05T12:00:00Z",
"updated_at": "2026-03-05T12:00:00Z"
}
}
}
Creating Boost or Bury Rules
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "boost-featured-drills",
"execution_order": 100,
"trigger": { "query": "drill", "match": "exact" },
"actions": {
"boosts": [
{ "condition": "extensions.products(Details).is_featured:=true", "weight": 100 },
{ "condition": "extensions.products(Details).is_clearance:=true", "weight": -100 }
]
}
}
}
}'
Contains trigger example
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "boost-milwaukee-cordless",
"execution_order": 200,
"trigger": { "query": "cordless", "match": "contains" },
"actions": {
"boosts": [
{ "condition": "extensions.products(Details).BRAND-NAME:=Milwaukee", "weight": 50 }
]
}
}
}
}'
Pinning and Hiding Products
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "pin-and-hide-products",
"execution_order": 300,
"trigger": { "query": "cordless drill", "match": "contains" },
"actions": {
"pins": [
{ "product_id": "978dfdd0-7cbf-454d-90b4-3d61de60ca46", "position": 1 }
],
"hides": [
{ "product_id": "15b0b18e-48bb-47c8-96e8-3d5739e68dfd" }
]
}
}
}
}'
Browse Rules
Browse rules apply to category pages, collection pages, and “view all products” experiences where the storefront sends q=*. They can still combine with shopper filters on that listing. They do not apply to autocomplete.
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "browse-featured-products",
"execution_order": 100,
"trigger": { "match": "browse" },
"actions": {
"filter_by": "extensions.products(Details).is_in_stock:=true",
"boosts": [
{ "condition": "extensions.products(Details).is_featured:=true", "weight": 100 }
]
}
}
}
}'
Query Rewrite Rules
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "rewrite-drill-typo",
"execution_order": 50,
"trigger": { "query": "dril", "match": "exact" },
"actions": { "replace_query": "drill" }
}
}
}'
Stopping Later Matching Rules
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"slug": "exclusive-sale-campaign",
"execution_order": 50,
"stop_after_match": true,
"trigger": { "query": "sale", "match": "exact" },
"actions": {
"boosts": [
{ "condition": "extensions.products(Details).is_sale:=true", "weight": 100 }
]
}
}
}
}'
Attaching Rule Groups to a Search Profile
A rule group has no effect until it is attached to a search profile:
curl -X PUT https://useast.api.elasticpath.com/v2/pcm/catalogs/search-profiles/{profile_id} \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_profile",
"attributes": {
"rule_groups": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440000"
]
}
}
}'
Rules for attaching groups:
- A profile can reference multiple rule groups; array order sets cross-group precedence.
- A profile may only reference rule groups visible to its tenant (organization/store scope follows the same model as search profiles).
- Set
rule_groupsto[]to detach all groups and disable the query-time rules engine for that profile.
Updating and Moving Rules
Update a rule directly, including a sparse execution_order:
curl -X PUT https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules/8fccaa19-dba9-4621-8d11-31a222a68c7c \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"execution_order": 150,
"actions": {
"boosts": [
{ "condition": "extensions.products(Details).is_featured:=true", "weight": 150 }
]
}
}
}
}'
Move a rule relative to another rule in the same group:
curl -X POST https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules/8fccaa19-dba9-4621-8d11-31a222a68c7c/move \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"type": "catalog_search_rule",
"attributes": {
"placement": "before",
"target_rule_id": "a1000001-0001-4000-8000-000000000099"
}
}
}'
placement supports before, after, first, and last. Supply target_rule_id only for before and after.
The service preserves sparse execution_order values when it can (for example moving a rule into an unused integer gap). When no gap remains between neighboring rules, the group is renumbered so each rule still has a unique execution_order.
Matched Rules in Search Responses
When rules match a full search request, the response can include matched_rules with the rule id and parent rule_group_id. The payload lists identifiers only — not the full rule bodies — so storefronts and tooling can confirm which campaigns fired.
Autocomplete (search-as-you-type) does not evaluate search rules and does not return matched_rules, even when the selected search profile has rule groups attached.
Deleting Rules and Rule Groups
Delete a single rule:
curl -X DELETE https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000/rules/8fccaa19-dba9-4621-8d11-31a222a68c7c \
-H "Authorization: Bearer $ACCESS_TOKEN"
Deleting a rule is allowed even when its parent group is still attached to a profile.
Delete a rule group:
curl -X DELETE https://useast.api.elasticpath.com/v2/pcm/catalogs/search/rule-groups/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $ACCESS_TOKEN"
A rule group cannot be deleted while any search profile still references it. Remove the group from every profile's rule_groups first, or the delete returns 409. Deleting a group cascade-deletes the rules inside it.
Organization and Store Scope
Rule groups follow the same organization and store tenancy model as search profiles:
- Organization-scoped groups can be attached to organization and store search profiles (subject to visibility rules for the tenant).
- Store-scoped groups can be attached only where that store's profiles allow.
Validation and Errors
| Status | When |
|---|---|
400 | Invalid slug, duplicate execution_order within a group, invalid trigger, empty actions, or invalid boost/filter syntax |
400 | Profile rule_groups contains an invalid UUID, a duplicate, or a group that does not exist / is out of scope |
403 | A store tries to update or delete an organization-scoped group or rule without permission |
404 | The rule group or rule does not exist |
409 | The rule group is still referenced by one or more search profiles |
Best Practices
- Attach groups intentionally. Creating rules alone does nothing until the group is on a profile's
rule_groupslist. - Choose the right trigger. Use
exactfor precise campaign queries,containsfor multi-word shopper phrasing, andbrowsefor category or all-products listings. - Plan
execution_order. Give overlapping rules intentional order so the earlier rule wins conflicts. - Use
stop_after_matchsparingly. Reserve it for exclusive campaigns that must block later matching rules. - Merchandise only on indexed fields. Boost, bury, and rule filters cannot use sale windows or custom relationships until those fields are indexed.
- Test with real storefront sorts. Confirm campaigns under the same profile, filters, and shopper sort options production will use.
- Detach before deleting a group. Remove the group from every profile first to avoid a
409.