--- title: Triggers description: Decide which requests a campaign fires on — the match tree, the request and catalog fields you can trigger on, and the endpoint that tells you what is authorable. slug: merchandising/business-rules/triggers docKind: guide hub: luigisbox-ai --- A campaign's `match` decides whether it applies to a given request. It is a condition tree, and every leaf field carries a `trigger.` prefix. Search requests whose query contains "shoes": ```json { "operator": "and", "conditions": [ { "field": "trigger.request:query", "operator": "matches", "value": "shoes" } ] } ``` Recommendations anchored on one product: ```json { "operator": "and", "conditions": [ { "field": "trigger.request:anchor_id", "operator": "eq", "value": "product/sku-1001" } ] } ``` Collection pages scoped to one category: ```json { "operator": "and", "conditions": [ { "field": "trigger.request:intent", "operator": "eq", "value": "collection" }, { "field": "trigger.@category", "operator": "eq", "value": "category/shirts" } ] } ``` The prefix is what distinguishes this tree from a rule's `record_match`: `trigger.` fields describe *the request*, unprefixed fields describe *a product*. ## What you can trigger on ### Fields from the request itself Four fields, under the reserved `request:` namespace: | Field | Value | |---|---| | `trigger.request:query` | The search query. `matches` fires when the value appears in the query as whole words, in order: `gift card` fires on "gift card for men", not on "card gift", and `sho` does not fire on "shoes". Case and accents are ignored. The comparison is on analyzed words, not raw text, so the catalog's language rules apply first — `shoe` can fire on "shoes", and a word the language treats as filler is dropped from both sides before the order is checked. | | `trigger.request:intent` | `search`, `recommender` or `collection` | | `trigger.request:type` | The requested object type, e.g. `product`. Present only when the request is scoped to a single type. | | `trigger.request:anchor_id` | A recommender anchor's ID in `type/value` form, e.g. `product/sku-1001` | `eq`, `neq`, `in` and `not_in` on `trigger.request:query` compare the whole query, case-insensitively. Any other `trigger.request:*` name is rejected with `422`, and so is a `trigger_ref` value: a trigger compares against literals only. A `match` with no conditions fires on every request in the targeting scope. ### Fields from the object the request is about Everything else is a **catalog attribute of the object the request is about** — the recommender's anchor, or the collection's scope: ```json { "field": "trigger.brand", "operator": "eq", "value": "brand/northwear" } ``` That fires the campaign on any recommendation anchored on a Northwear product, or any collection page scoped to that brand. It says nothing about which products the rules then affect — that is `record_match`. A recommendation with several anchors fires the campaign when any one of the first ten anchors satisfies the conditions. A collection exposes its scope when the scope is an AND of equality or membership conditions (`@category IN ("category/shirts")`, `brand = "brand/northwear"`), one attribute per condition, written exactly as in the request's `collection_filters`. A scope using OR, NOT or a range exposes nothing, so no *positive* condition on a catalog attribute can fire on it — what a negative one does is the next paragraph. The shopper's own `filters` never trigger a campaign. A search request has no such object. A positive condition on a catalog attribute (`eq`, `in`, a comparison) therefore never fires on search. A negative one (`neq`, `not_in`, or a `not` group) fires on **every** search request, because the attribute is missing there and a negative test on a missing attribute is true. The same holds for a recommendation with no anchor and for a collection that exposes no attributes. If the attribute condition should decide which products are affected rather than when the campaign fires, put it on the rule instead. ## Discovering what you can trigger on `POST /platform/v1/campaigns/trigger-schema` takes a targeting set and returns the trigger fields authorable across it, with the exact operators each one accepts: :::hub[Config → Business Rules → Settings]{path="/business-rules"} The campaign editor's condition builder is populated from this endpoint, so it only ever offers fields and operators the campaign's targeting can actually authorise. Call it directly when you build conditions outside the Hub. ::: ```bash curl -X POST 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/trigger-schema' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"organization_id": "lbo_r2vn8c", "catalog_ids": ["lbc_8w3k2p"]}' ``` Each entry carries a `name` (unprefixed — add `trigger.` to use it), a `source` of `catalog` or `request`, the `operators` it accepts, and a `value_domain` saying whether the `values` list is binding: | `value_domain` | Meaning | |---|---| | `closed` | A fixed choice; anything else is rejected on save | | `suggested` | What the scope carries today, but other values are accepted | | `open` | Free input; `values` is empty | `values` is populated for `trigger.request:intent` (`closed`) and `trigger.request:type` (`suggested`); the other two request fields and every catalog attribute are `open` with an empty list. For a catalog attribute, read the values from [catalog metadata](/indexing/catalog-metadata/) instead. The response is [paginated](/api-basics/requests-and-responses/#pagination), and `query` narrows it to a case-insensitive substring of the field name — a large catalog can carry a lot of attributes. Two reasons to call this endpoint rather than assume the field names: - **The operators it returns are the ones that will be accepted on save.** If you are building an authoring UI, offer exactly these and a save can never be rejected on operator grounds. They also tell you which value input to render: range and comparison operators mean the field is numeric, membership-only means it is a keyword. - **It answers for the whole targeted scope, conservatively.** A field that is numeric in one targeted catalog and text in another is reported with the keyword-safe operator set, because that is the set that will survive validation across every catalog in scope. ## Condition syntax The same syntax serves `match` and `record_match`. A condition tree is `operator` plus `conditions`, nestable: ```json { "operator": "and", "conditions": [ { "field": "brand", "operator": "eq", "value": "brand/northwear" }, { "operator": "or", "conditions": [ { "field": "color", "operator": "eq", "value": "blue" }, { "field": "color", "operator": "eq", "value": "navy" } ] } ] } ``` Group operators are `and`, `or`, `not`. Leaf operators depend on the field's type: | Field | Operators | |---|---| | Text, and any field the catalog has no type for | `eq`, `neq`, `in`, `not_in`, `exists` | | Numeric | the above plus `gt`, `gte`, `lt`, `lte`, `range` | | Boolean | `eq`, `neq`, `exists` | | `trigger.request:intent`, `trigger.request:type`, `trigger.request:anchor_id` | as text | | `trigger.request:query` | as text, plus `matches` | `matches` works only on `trigger.request:query`: not on any other trigger field, and not in a rule's `record_match`. The [trigger-schema endpoint](#discovering-what-you-can-trigger-on) reports the exact set for each field. A tree may nest up to 10 levels deep and hold up to 100 conditions in total. Past either, the save is rejected; split a tree that large into several rules. The evaluation rules are the same as [discovery filters](/discovery/filters/), including the [missing-attribute rule](/discovery/filters/#missing-attributes). The tree is written differently, and campaigns accept two operators discovery does not — `matches` and `range`. See [Other surfaces that filter](/discovery/filters/#other-surfaces-that-filter). ## See also - [Rules and effects](/merchandising/business-rules/rules-and-effects/) — the other condition tree - [Targeting](/merchandising/business-rules/targeting/) — where a campaign can act - [Catalog metadata](/indexing/catalog-metadata/) — which attributes and values exist