Documentation Index

Fetch the complete documentation index at: https://docs.xgen.ai/llms.txt

Use this file to discover all available pages before exploring further.

Merchandising Rules V3

Prev Next

# Merchandising V3 Filters and Facets

This guide explains how Merchandising V3 decides which products appear in a rule, how facets are calculated, and what each operator and facet option means. It is written for business users who configure merchandising rules and for technical users who need to understand the saved rule JSON.

Overview

Merchandising V3 rules have two main parts:

  • Filters decide which products are eligible to appear.
  • Facets count and group values from those eligible products so a storefront, preview, or API response can show filter options such as material, color, size, category, or price.

A filter is built from one or more conditions. Each condition asks a question about a product, user, current product, or runtime value. For example:

  • Is the product material equal to Cotton?
  • Is the product price less than or equal to 100?
  • Does the product have at least one of the selected colors?
  • Does the category path start with one of the selected category paths?

Facets do not filter products by themselves. They summarize the matching product set. For example, after a rule finds 250 matching products, a Material facet may return Cotton: 83, Linen: 54, and Wool: 21.

Rule Logic

At the top of the filter builder, the UI asks whether products should match ALL or ANY of the conditions.

UI option JSON value Meaning
ALL and A product must match every condition in the group.
ANY or A product only needs to match one condition in the group.

Groups can be nested. A common pattern is:

  • Match ALL of:
  • Product is in stock.
  • Product matches ANY of several materials.

In JSON, a group uses logic and conditions:

{
  "version": "3",
  "logic": "and",
  "conditions": [
    { "property": "in_stock", "operator": "equals", "value": true },
    {
      "logic": "or",
      "conditions": [
        { "property": "material", "operator": "equals", "value": "Cotton" },
        { "property": "material", "operator": "equals", "value": "Linen" }
      ]
    }
  ]
}

Condition Fields

Each condition has three core choices in the UI:

UI field JSON field Meaning
Field property The product field to evaluate, such as material, price, categories, or metadata.color.
Operator operator The comparison to use, such as is equal to, contains, or is one of.
Value type value or variable Where the comparison value comes from.

Value Types

UI value type JSON behavior Use when
Static Value Uses value The condition always compares against the same typed value, such as true, 100, or Cotton.
Variable Uses variable The value is provided at runtime, such as selected filters from a storefront.
User Property Uses variable with an xu_ value The value comes from user behavior, such as viewed products or purchased products.
Current Product Uses variable with an xp_ value The value comes from the product currently being viewed. In order to use this, you MUST pass pdpCode as context.

Runtime variables can use "*" as a pass-through value. When the resolved comparison value is "*", the condition passes for all products.

User Properties

The UI supports these user property variables:

UI label Variable
Viewed Products xu_viewed_products
Cart Items xu_cart_items
Most Recent Cart Product xu_recent_cart_product
Most Recent Cart Category xu_recent_cart_category
Viewed Multiple Times xu_multi_viewed_products
Purchased Products xu_purchased_products
Most Recent Viewed Product xu_last_viewed
Most Recent Viewed Category xu_last_viewed_category

User property values are cached for a limited time.

Current Product Properties

Current product values are saved as variables prefixed with xp_. For example, selecting current product field material stores variable: "xp_material". Then when pdpCode is sent as context, it is replaced with the value for material on that specific product passed.

Operator Reference

The UI shows friendly operator labels. The saved JSON uses operator slugs. Aliases are also accepted when importing JSON.

UI label Primary slug Aliases Works best with Meaning
is equal to equals =, ===, equal_to, is, is_equal_to string, number, boolean Product value must exactly equal the comparison value. No type coercion.
is not equal to doesnt_equal !=, !==, not, not_equal, not_equal_to, is_not, is_not_equal_to, doesnt_equal_to, !equals string, number, boolean Product value must not exactly equal the comparison value. No type coercion.
contains contains has, includes string, list Product string contains a substring, or product list contains a value.
does not contain doesnt_contain !contains, !has, !includes, doesnt_have, not_contains string, list Product string/list does not contain the comparison value.
is greater than greater_than > number Product number is greater than the comparison value. Numeric strings can be coerced.
is greater than or equal to greater_than_or_equal_to >=, gte number Product number is at least the comparison value. Numeric strings can be coerced.
is less than less_than < number Product number is less than the comparison value. Numeric strings can be coerced.
is less than or equal to less_than_or_equal_to <=, lte number Product number is at most the comparison value. Numeric strings can be coerced.
is one of any some, in string, list Product value is present in the comparison list. This is the reverse of contains.
is not one of none not_in string, list Product value is not present in the comparison list.
has at least one of has_one_of none list Product list and comparison list share at least one item.
has none of has_none_of none list Product list and comparison list do not share any items.
has all of all every list Product list contains every item in the comparison list.
starts with any path path_prefix_any none list Product hierarchy path starts with at least one selected path prefix.
matches pattern matches_regex regex string Product value matches a regular expression pattern.
has a value exists exist, is_not_null, is_defined string, number, boolean, list Field is present and not null. No comparison value is needed.
has no value is_null is_empty, is_not_defined string, number, boolean, list Field is missing or null. No comparison value is needed.

Operator Notes

contains and is one of are similar but point in opposite directions:

  • contains: the product field is the collection or string. Example: product tags contains "sale".
  • is one of: the comparison value is the collection. Example: product material is one of ["Cotton", "Linen"].

Use has_one_of for multi-select filtering when both sides are lists:

{
  "property": "colors",
  "operator": "has_one_of",
  "variable": "selected_colors"
}

Use path_prefix_any for nested category facets:

{
  "property": "categories",
  "operator": "path_prefix_any",
  "variable": "selected_categories"
}

With context:

{
  "selected_categories": [["Furniture", "Living Room"], ["Lighting"]]
}

An item with categories: ["Furniture", "Living Room", "Sofas"] matches because its path starts with ["Furniture", "Living Room"].

Facets

Facets define which filter options should be returned with the matching products. A facet selects a field, a behavior, and optional display/grouping controls.

Example:

{
  "property": "material",
  "label": "Material",
  "mode": "disjunctive",
  "exclude": ["material_filter"],
  "order_by": "count_desc",
  "manual_order_list": ["Cotton", "Linen"],
  "omit": ["Unknown", ""],
  "value_type": { "type": "" }
}

Facet Fields

UI field JSON field Required Meaning
Field property Yes Product field to count.
Display label label No Customer-facing name. Defaults to the field name if empty.
Behavior mode Yes How counts are calculated: narrows results or multi-select.
Linked condition or filter group exclude For multi-select behavior Condition/group IDs to ignore while calculating this facet's counts.
Value grouping value_type No Controls whether values are listed individually, grouped into ranges, returned as min/max, or treated as a hierarchy.
Sort values by order_by No Controls bucket order for regular and nested facets.
Manual order manual_order_list No Pins selected values to the front in a specific order.
Hide specific values omit No Removes values from the facet response.

Facet Behavior

Narrows Results

UI label: Narrows results

JSON mode: conjunctive

Counts are calculated from the already-filtered product set. Selecting a value narrows the results further (used for single select options).

Use this for simple refinement behavior where each selected option should reduce the available pool.

{
  "property": "color",
  "label": "Color",
  "mode": "conjunctive"
}

Multi-select

UI label: Multi-select

JSON mode: disjunctive

Counts are calculated as if the linked condition or group were temporarily ignored. This lets a facet continue showing other available values even after one value is selected.

Use this for common multi-select storefront filters such as material, color, size, or category.

For multi-select to work well:

  • The facet should link to the condition or group that controls the same selection.
  • The linked condition should use a runtime variable, user property, or current product property.
  • The linked condition or group must have an id.

Example condition:

{
  "id": "material_filter",
  "property": "material",
  "operator": "any",
  "variable": "selected_materials"
}

Example linked facet:

{
  "property": "material",
  "label": "Material",
  "mode": "disjunctive",
  "exclude": ["material_filter"]
}

If a group is linked, only variable-based conditions inside that group are ignored for the count calculation. Static conditions remain applied.

Facet Value Grouping

Enumerate Distinct Values

UI label: Enumerate Distinct Values (default)

JSON:

{ "value_type": { "type": "" } }

This creates one bucket for each distinct value. It is best for material, color, size, availability, rating labels, and similar fields.

If the field is a list, each list item becomes its own bucket.

Example response:

{
  "property": "color",
  "values": [
    { "display_value": "Blue", "value": "Blue", "count": 42 },
    { "display_value": "Red", "value": "Red", "count": 35 }
  ]
}

Number Ranges

UI label: Number ranges (intervals)

JSON:

{
  "value_type": { "type": "interval", "interval": 50 }
}

This groups numeric values into fixed-width ranges. With an interval of 50, prices are grouped as 0-50, 50-100, 100-150, and so on.

Use this for price, inventory quantity, rating count, discount percentage, or other numeric ranges.

Notes:

  • interval must be greater than 0.
  • Non-numeric values are skipped.
  • Interval facets are always sorted by range from low to high.
  • The order_by setting is ignored for interval facets.

Example response:

{
  "property": "price",
  "values": [
    { "display_value": "0-50", "value": [0, 50], "count": 10 },
    { "display_value": "50-100", "value": [50, 100], "count": 24 }
  ]
}

Min / Max Values

UI label: Min / Max values

JSON:

{
  "value_type": { "type": "min_max" }
}

This returns the minimum and maximum numeric values found in the matching product set. It is useful when the storefront needs to render a slider.

Notes:

  • Only numeric values are considered.
  • If the field is a list, all numeric values in the list are considered.
  • If no numeric values are found, the facet returns no buckets.
  • If min and max are the same, both buckets may have the same value.

Example response:

{
  "property": "price",
  "values": [
    { "display_value": "19.99", "value": 19.99, "count": 5 },
    { "display_value": "299.99", "value": 299.99, "count": 3 }
  ]
}

Nested Hierarchy

UI label: Nested hierarchy

JSON:

{
  "value_type": { "type": "nested" }
}

This builds a tree from a list field where each position represents a level in the hierarchy.

Example product field:

{
  "categories": ["Furniture", "Living Room", "Sofas"]
}

Example nested facet response:

{
  "property": "categories",
  "values": [
    {
      "display_value": "Furniture",
      "value": "Furniture",
      "count": 2,
      "children": [
        {
          "display_value": "Living Room",
          "value": "Living Room",
          "count": 2,
          "children": [
            { "display_value": "Sofas", "value": "Sofas", "count": 1 },
            { "display_value": "Coffee Tables", "value": "Coffee Tables", "count": 1 }
          ]
        }
      ]
    }
  ]
}

Use path_prefix_any to filter products by selected nested facet paths.

Sorting Facet Values

The UI option Sort values by maps to order_by.

UI label JSON value Meaning
A to Z alphabetical_asc Sort by display value ascending.
Z to A alphabetical_desc Sort by display value descending.
Most popular first count_desc Highest count first, with alphabetical tie-break.
Least popular first count_asc Lowest count first, with alphabetical tie-break.
Lowest number first numeric_asc Sort by numeric value ascending. Values must be numeric.
Highest number first numeric_desc Sort by numeric value descending. Values must be numeric.

If omitted, facet values default to alphabetical ascending.

For nested hierarchy facets, sorting applies independently at each level of the tree.

Manual Ordering

Manual order pins specific display values to the front of a facet response. Any values not included in the manual list still appear afterward using the selected sort order.

Example:

{
  "property": "color",
  "order_by": "count_desc",
  "manual_order_list": ["Black", "White", "Red"]
}

In this example, Black, White, and Red appear first in that order. Remaining colors appear by count descending.

Manual values must match the facet value's display_value. Values that do not exist in the result set are ignored.

Hiding Facet Values

The omit list hides facet values entirely. This should be done by the display_value

Use it to remove values such as:

  • Empty string: ""
  • No value: "null"
  • Placeholder values such as -
  • Internal labels such as Unknown

Example:

{
  "property": "material",
  "omit": ["", "Unknown", "-"]
}

For nested hierarchy facets, omitting a node also omits its children.

Deduplication

The rule setting Deduplication field maps to dedup_field in JSON.

Use deduplication when multiple catalog records represent the same shopper-facing product, such as size variants or color variants.

Example:

{
  "version": "3",
  "logic": "and",
  "conditions": [],
  "dedup_field": "master_product_id"
}

Products are filtered first, then deduplicated by the selected field.

Full Example

This example filters to in-stock products, supports multi-select material filtering, supports nested category filtering, deduplicates by sku, and returns material, price, and category facets.

{
  "version": "3",
  "logic": "and",
  "conditions": [
    {
      "property": "in_stock",
      "operator": "equals",
      "value": true
    },
    {
      "id": "material_filter",
      "property": "material",
      "operator": "any",
      "variable": "selected_materials"
    },
    {
      "id": "category_filter",
      "property": "categories",
      "operator": "path_prefix_any",
      "variable": "selected_categories"
    }
  ],
  "dedup_field": "master_id",
  "facets": [
    {
      "property": "material",
      "label": "Material",
      "mode": "disjunctive",
      "exclude": ["material_filter"],
      "order_by": "count_desc",
      "manual_order_list": ["Cotton", "Linen"],
      "omit": ["", "Unknown"],
      "value_type": { "type": "" }
    },
    {
      "property": "price",
      "label": "Price",
      "mode": "conjunctive",
      "value_type": { "type": "min_max" }
    },
    {
      "property": "categories",
      "label": "Category",
      "mode": "disjunctive",
      "exclude": ["category_filter"],
      "order_by": "alphabetical_asc",
      "value_type": { "type": "nested" }
    }
  ]
}

Runtime context example:

{
  "selected_materials": ["Cotton", "Linen"],
  "selected_categories": [["Shoes", "Running"]]
}

Troubleshooting

Issue Likely cause Resolution
Save is disabled because a condition needs a field. A condition was added without selecting a product field. Select a field or remove the condition.
Save is disabled because a condition needs a value. The operator requires a value, but the value is blank. Enter a static value or choose a variable/user/current-product source.
A multi-select facet does not show other available values. The facet is not linked to the condition controlling that selection. Set Behavior to Multi-select and choose the linked condition or group.
A disjunctive facet still looks filtered. The linked condition uses a static value, or the wrong condition/group is linked. Use a variable-based condition for selected values and link that condition's ID.
Numeric sorting fails or behaves unexpectedly. The facet value is not numeric. Use alphabetical/count sorting, or clean the catalog field so values are numeric.
Interval facet is invalid. Interval size is blank, zero, or negative. Enter an interval size greater than 0.
A nested category facet is flat or empty. The category field is not stored as a path array. Store each product category as a flat ordered array, such as ["Furniture", "Living Room", "Sofas"].
A filter unexpectedly matches everything. Runtime context value is "*". Replace "*" with the intended selected value, unless pass-through behavior is desired.

Best Practices

  • Use ALL for required business rules, such as in-stock, active, correct locale, or not discontinued.
  • Use ANY groups for alternatives, such as several acceptable materials or categories.
  • Use variables for shopper-selected filters so storefront selections can change without editing the rule.
  • Use has_one_of when both the product field and selected values are lists.
  • Use any when the product field is a single value and the selected values are a list.
  • Use disjunctive, multi-select facets for common storefront filter groups.
  • Give linked conditions readable IDs such as material_filter, color_filter, or category_filter.
  • Use omit to hide blank, unknown, or placeholder values before exposing facets to shoppers.
  • Use min_max for slider controls and interval for fixed price buckets.
  • Use nested plus path_prefix_any for hierarchical categories.