> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselayers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Filtering language

> How to construct complex and nested filter expressions for the Search and Browse APIs, including comparison operators, boolean groups, and field selectors.

## Filter basics

The `filter_group` API parameter refines results by selecting only the products that match the given expressions and running the query on those products. The `filter_group` expects a **filter expression**.

```json theme={null}
{
  "filter_group": {
    "conditional": "AND",
    "expressions": [
      {
        "property": "product_type",
        "operator": "eq",
        "values": ["Sweater"]
      },
      {
        "property": "options.color",
        "operator": "eq",
        "values": ["RED"]
      }
    ]
  }
}
```

## Expressions and operators

Expressions are the core of the filter language.

```json theme={null}
{
  "property": "product_type",
  "operator": "eq",
  "values": ["Sweaters"]
}
```

* `property` is the attribute of the field you want to filter on.
* `operator` is the logical operator to apply (see [Supported operators](#supported-operators)).
* `values` is an array of values the `operator` compares against in the `property`.

### Supported operators

| Operator | Description |
| - | - |
| `eq` | Equal to a single value |
| `neq` | Not equal to a single value |
| `in` | Matches any value in the array |
| `notIn` | Does not match any value in the array |
| `gt` | Greater than |
| `gte` | Greater than or equal to |
| `lt` | Less than |
| `lte` | Less than or equal to |
| `between` | Within a range (pass exactly two values) |
| `notBetween` | Outside a range (pass exactly two values) |
| `contains` | String contains the given value (case-insensitive) |
| `doesNotContain` | String does not contain the given value (case-insensitive) |
| `beginsWith` | String starts with the given value (case-insensitive) |
| `endsWith` | String ends with the given value (case-insensitive) |
| `doesNotBeginWith` | String does not start with the given value (case-insensitive) |
| `doesNotEndWith` | String does not end with the given value (case-insensitive) |
| `null` | Value is null or does not exist |
| `notNull` | Value is not null (exists) |
| `after` | Date/timestamp value is strictly after the given bound (pass exactly one value) |
| `before` | Date/timestamp value is strictly before the given bound (pass exactly one value) |
| `inLast` | Date/timestamp value falls within the last *N* days, evaluated at query time (pass a single integer day count) |
| `geoRadius` | Geo attribute lies within a radius of an origin (see [Geospatial filtering](https://docs.uselayers.com/engine/geospatial-filtering.md)) |
| `geoBoundingBox` | Geo attribute lies inside a bounding box |
| `geoPolygon` | Geo attribute matches a GeoJSON polygon |

### Negative operators and missing values

Negative operators (`neq`, `notIn`, `notBetween`, `doesNotContain`, `doesNotBeginWith`, `doesNotEndWith`) also match products that have no value for the attribute. For example, `tags notIn ["Outlet"]` returns untagged products as well as products tagged with anything other than `Outlet`. This applies consistently across the Search and Browse APIs.

If you want to exclude products that are missing the attribute entirely, combine the negative condition with `notNull` in an `AND` group:

```json theme={null}
{
  "conditional": "AND",
  "expressions": [
    {
      "property": "tags",
      "operator": "notIn",
      "values": ["Outlet"]
    },
    {
      "property": "tags",
      "operator": "notNull",
      "values": []
    }
  ]
}
```

### Examples of basic conditions

A condition to find products of type `Sweaters`:

```json theme={null}
{
  "property": "product_type",
  "operator": "eq",
  "values": ["Sweaters"]
}
```

A condition to find products created after 1 January 2020 (UNIX Epoch time):

```json theme={null}
{
  "property": "created_at",
  "operator": "gt",
  "values": [1577836800]
}
```

A condition to exclude gift card products:

```json theme={null}
{
  "property": "is_gift_card",
  "operator": "eq",
  "values": [false]
}
```

A condition to find bundle products (products with variants that require components):

```json theme={null}
{
  "property": "has_variants_that_require_components",
  "operator": "eq",
  "values": [true]
}
```

A condition to find products within a price range (e.g., between $10 and $50):

```json theme={null}
{
  "property": "variants.price",
  "operator": "between",
  "values": [10, 50]
}
```

A condition to find products with variants available at specific location IDs (e.g., locations 1001 and 1002):

> [!WARNING]
>
> The `variants.in_stock_location_ids` field is not filterable. It is a **sort-only** field used for location-based product demotion in sort orders. See [Sorting](https://docs.uselayers.com/platform/sorting.md) for details on using this field in priority rules.
>
> To filter by location, including "available for pickup near me", use `variants.pickup_locations`. See [Local pickup filtering](https://docs.uselayers.com/engine/geospatial-filtering.md#local-pickup-filtering).

```json theme={null}
{
  "property": "variants.pickup_locations",
  "operator": "in",
  "values": [1001, 1002]
}
```

A condition to find products that have at least one variant on sale (variant compare-at price greater than its price):

```json theme={null}
{
  "property": "on_sale",
  "operator": "eq",
  "values": [true]
}
```

Use `variants.on_sale` instead to restrict to specific variants on sale, for example when combined with other variant-level conditions in the same expression group. See [On-sale attributes](https://docs.uselayers.com/platform/attributes/catalog-attributes.md#on-sale-attributes) for how the values are derived.

A condition to find products whose title contains "organic":

```json theme={null}
{
  "property": "title",
  "operator": "contains",
  "values": ["organic"]
}
```

A condition to exclude products without a vendor (null check):

```json theme={null}
{
  "property": "vendor",
  "operator": "notNull",
  "values": []
}
```

A condition to find products NOT in a price range (e.g., outside $10 to $50):

```json theme={null}
{
  "property": "variants.price",
  "operator": "notBetween",
  "values": [10, 50]
}
```

A condition to find products published in the last 30 days. `inLast` is evaluated against `NOW()` at query time, so the window stays evergreen. You can save it once on a sort order or merchandising rule and it tracks the calendar without re-saving:

```json theme={null}
{
  "property": "published_at",
  "operator": "inLast",
  "values": [30]
}
```

A condition to find products created strictly after a fixed date:

```json theme={null}
{
  "property": "created_at",
  "operator": "after",
  "values": ["2025-01-01"]
}
```

A condition to find products created strictly before a fixed date:

```json theme={null}
{
  "property": "created_at",
  "operator": "before",
  "values": ["2025-01-01"]
}
```

> [!NOTE]
>
> `inLast`, `after`, and `before` are designed for attributes whose value type is **date** or **timestamp**. Both date-only values such as `"2025-01-01"` and full timestamp values such as `"2025-01-01T00:00:00Z"` are accepted.

## Complex filter expressions

You can build advanced filter groups by combining expressions using `AND` and `OR`.

### Example of complex expression

To include both `Accessories` and products from `SUPREME` published after 1 January 2020:

```json theme={null}
{
  "conditional": "OR",
  "expressions": [
    {
      "conditional": "AND",
      "expressions": [
        {
          "property": "product_type",
          "operator": "eq",
          "values": ["Accessories"]
        },
        {
          "property": "vendor",
          "operator": "eq",
          "values": ["Supreme"]
        }
      ]
    },
    {
      "conditional": "AND",
      "expressions": [
        {
          "property": "published_at",
          "operator": "gt",
          "values": ["1577836800"]
        }
      ]
    }
  ]
}
```

### Variant-level conditions

Conditions on variant-level properties such as `options.*`, `variants.*`, and `variant_metafields.*` are matched against individual variants. When a group combines several variant-level conditions with `AND`, a single variant must satisfy all of them. A product that has a size M variant and a separate red variant does not match `options.size eq M` AND `options.color eq Red`.

The `available` filter follows the same rule when you combine it with at least one variant-level condition in the same `AND` group. The variant that matches the other conditions must itself be in stock. For example, this filter returns only products that have an in-stock size M variant:

```json theme={null}
{
  "conditional": "AND",
  "expressions": [
    {
      "property": "options.size",
      "operator": "eq",
      "values": ["M"]
    },
    {
      "property": "available",
      "operator": "eq",
      "values": [true]
    }
  ]
}
```

A product whose only M variant is sold out is excluded, even if other sizes are in stock. Layers checks variant availability with the same rules it uses to hide or demote out-of-stock products, including location-scoped inventory and variants set to continue selling when out of stock. On its own, `available` still matches any product that is in stock.

Variant-level conditions follow these rules:

* **Shopper and merchant filters combine.** Variant-level conditions from the request's `filter_group` and from merchandising or collection filters form one `AND` group. The same variant must satisfy all of them.
* **`OR` branches match independently.** Within each `OR` branch, one variant must satisfy all of that branch's variant-level conditions. Different branches can match different variants.
* **The matched variant is returned.** [`first_or_matched_variant`](https://docs.uselayers.com/developers/product-schema/variant-data) returns the variant that satisfied the conditions, so the tile shows that variant's image, price, and options.
* **Facet counts follow the matched variant.** With `options.size eq M` and `available` selected, the color facet counts only the colors of in-stock M variants. Sibling variants in other sizes are not counted.

## Best practices

When crafting filter expressions:

* Test complex expressions in a controlled environment before deployment.
* String operators (`contains`, `doesNotContain`, `beginsWith`, `endsWith`, `doesNotBeginWith`, `doesNotEndWith`) are case-insensitive. For example, filtering where title `contains` "organic" also matches "Organic" and "ORGANIC".
* Use the `between` operator instead of combining `gte` and `lte` for range queries.
* Nested filter groups support up to two levels of depth.
* Every nested group must contain at least one expression; empty nested groups are rejected.

## Troubleshooting

If your filter expression isn't returning the expected results:

* Verify that operator names use the exact camelCase format listed in [Supported operators](#supported-operators) (e.g., `notIn`, not `not_in`).
* Confirm that attribute names and values match exactly with the data.
* Ensure the `values` field is always an array, even for single-value operators like `eq`.
* Check that the `conditional` field uses uppercase `AND` or `OR`.
