> For the complete documentation index, see [llms.txt](https://docs.akinon.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akinon.com/tutorials/commerce/how-to-use-dynamic-filter-and-exclude.md).

# How to Use Dynamic Filter and Dynamic Exclude

Dynamic Filter and Dynamic Exclude let any HTTP client shape search results at request time by passing extra filter or exclusion conditions as request headers. The feature sits entirely on the Commerce (omnishop) side; the client — whether a BFF (Backend For Frontend) service, a mobile application, or any other HTTP consumer — only needs to attach the appropriate header.

**Dynamic Filter** narrows the result set to only the products matching the conditions you specify — useful when you want to work within a defined subset of products (e.g. recently viewed, mobile-exclusive, country-specific catalogue).

**Dynamic Exclude** removes specific products from the result set — useful when you want the full catalogue minus certain items (e.g. discontinued variants, channel-restricted brands).

Unlike static facet configuration, which is set once at the channel level in the admin, dynamic filter and exclude conditions are sent per-request by the client and take effect immediately with no configuration change needed.

Both headers can be sent together in the same request. Commerce evaluates both conditions simultaneously as part of a single Elasticsearch query — results must match the filter conditions AND not match the exclude conditions.

For the low-level configuration reference (facet field definitions, `SEARCH_DYNAMIC_FILTER_ACTIVE`, etc.) see [Facet and Sort Configuration](https://docs.akinon.com/technical-guides/commerce/filtering-products#dynamic-filter).

## <mark style="color:red;">Enabling the Feature</mark>

Set the environment variable below to `True` in your Commerce project before using any of the examples in this tutorial.

```
SEARCH_DYNAMIC_FILTER_ACTIVE=True
```

If you are using omnife as a BFF, set the same variable there as well so that omnife forwards the headers to the Commerce project.

```
SEARCH_DYNAMIC_FILTER_ACTIVE=True
```

{% hint style="info" %}
If you are using `products.attributes_*` fields as filter or exclude conditions, the corresponding attribute must have `is_filterable` set to `true` in the attribute configuration in Omnitron.
{% endhint %}

## <mark style="color:red;">How It Works</mark>

### Difference from Standard Query Parameter Filters

The Commerce `/list/` endpoint also accepts standard query parameter filters such as `/list/?attributes_size=S`. These are user-driven: the end user selects them from the filter panel, they appear in the URL, and they require a `FacetConfiguration` to be defined in the admin first.

Dynamic Filter and Dynamic Exclude are client-driven: they are set programmatically by the client (a BFF service, a mobile app, etc.), are invisible to the end user in the URL, and cannot be manipulated by the end user. No admin configuration is needed beyond enabling the feature.

Both mechanisms work simultaneously — for example, a header can narrow the catalogue to country-specific products while the user applies a size filter via query parameters on top of that.

### Headers

Dynamic Filter and Dynamic Exclude are supported on both the `/list/` and `/autocomplete/` endpoints.

Every request to these endpoints may carry two optional headers:

| Header                     | Purpose                                           |
| -------------------------- | ------------------------------------------------- |
| `X-SEARCH-DYNAMIC-FILTER`  | Include only products that match these conditions |
| `X-SEARCH-DYNAMIC-EXCLUDE` | Exclude products that match these conditions      |

Both headers carry a **base64-encoded JSON object**. The JSON object maps Elasticsearch field names to lists of values.

**Encoding a filter in Python:**

```python
import base64
import json

payload = {"products.attributes_available_country": ["tr"]}
header_value = base64.b64encode(json.dumps(payload).encode()).decode()
# eyJwcm9kdWN0cy5hdHRyaWJ1dGVzX2F2YWlsYWJsZV9jb3VudHJ5IjogWyJ0ciJdfQ==
```

**Logic rules:**

* Multiple values in the same list → **OR** (any value matches)
* Multiple keys in the same object → **AND** (all conditions must match)
* `_any_of` key with a list of condition objects → **OR across groups**

## <mark style="color:red;">Use Cases</mark>

### <mark style="color:red;">1. Mobile-Exclusive Products</mark>

A mobile application sells products that are marked with a custom attribute `is_mobile` set to the string value `"true"`. By attaching the filter header on every search request the mobile client makes, those products become visible only to that client.

**JSON payload:**

```json
{"products.attributes_is_mobile": ["true"]}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJwcm9kdWN0cy5hdHRyaWJ1dGVzX2lzX21vYmlsZSI6IFsidHJ1ZSJdfQ=='
```

The same pattern works in reverse: a web client can exclude mobile-only products by sending the same payload as an `X-SEARCH-DYNAMIC-EXCLUDE` header, so each channel naturally sees only its own catalogue without any separate index or product configuration.

***

### <mark style="color:red;">2. Country-Specific Catalogues (Multi-site)</mark>

A single Commerce instance serves multiple storefronts — for example, `example.com.tr` and `example.com.fr`. Products carry an `available_country` attribute set to `tr` or `fr`. Each storefront's BFF applies the appropriate filter on every outgoing request so users never see products meant for another market.

**TR site — JSON payload:**

```json
{"products.attributes_available_country": ["tr"]}
```

**FR site — JSON payload:**

```json
{"products.attributes_available_country": ["fr"]}
```

**omnife `GLOBAL_HEADERS_FUNCTION` example (TR site settings):**

```python
import base64
import json

def global_headers_function(request=None):
    payload = {"products.attributes_available_country": ["tr"]}
    encoded = base64.b64encode(json.dumps(payload).encode()).decode()
    return {"X-Search-Dynamic-Filter": encoded}

GLOBAL_HEADERS_FUNCTION = global_headers_function
```

`GLOBAL_HEADERS_FUNCTION` is called for every request omnife forwards to the Commerce project. The function receives the current Django `request` object, so the header value can be built from session data, request metadata, or any other runtime context.

***

### <mark style="color:red;">3. Recently Viewed Products</mark>

A "Continue browsing" section on the homepage needs to retrieve the last few products a user viewed, preserving their original order. The client stores the viewed product PKs (e.g. in a cookie or localStorage) and sends them as a filter.

**JSON payload:**

```json
{"products.pk": [101, 102, 103]}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJwcm9kdWN0cy5wayI6IFsxMDEsIDEwMiwgMTAzXX0='
```

To return results in exactly the order the PKs were listed, combine with `default_sorting_deactivated` (see [Use Case 6](#6-editorial-product-ordering)).

***

### <mark style="color:red;">4. Products You've Already Ordered</mark>

A "Your Previous Purchases" page shows only the products a logged-in user has ordered before. The client fetches the user's order history from the `/users/orders/` endpoint, collects the product IDs, and sends them as a filter so Commerce returns only those products with their current stock and pricing.

**JSON payload:**

```json
{"products.pk": [45, 78, 112, 203]}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJwcm9kdWN0cy5wayI6IFs0NSwgNzgsIDExMiwgMjAzXX0='
```

***

### <mark style="color:red;">5. Filtering with Multiple Condition Groups (</mark><mark style="color:red;">`_any_of`</mark><mark style="color:red;">)</mark>

Use `_any_of` when you need OR logic across different fields. Each object inside `_any_of` is evaluated with AND logic; the objects themselves are combined with OR logic. In the example below, the request returns products that are either (Red AND size M) or (Blue AND size L).

**JSON payload:**

```json
{
    "_any_of": [
        {
            "products.attributes_color": ["Red"],
            "products.attributes_size": ["M"]
        },
        {
            "products.attributes_color": ["Blue"],
            "products.attributes_size": ["L"]
        }
    ]
}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJfYW55X29mIjogW3sicHJvZHVjdHMuYXR0cmlidXRlc19jb2xvciI6IFsiUmVkIl0sICJwcm9kdWN0cy5hdHRyaWJ1dGVzX3NpemUiOiBbIk0iXX0sIHsicHJvZHVjdHMuYXR0cmlidXRlc19jb2xvciI6IFsiQmx1ZSJdLCAicHJvZHVjdHMuYXR0cmlidXRlc19zaXplIjogWyJMIl19XX0='
```

***

### <mark style="color:red;">6. Editorial Product Ordering</mark>

A merchandiser wants a curated landing page that shows three specific products in a fixed, hand-picked order — not ranked by relevance or popularity. Setting `default_sorting_deactivated` to `true` inside the filter payload disables the default sort and returns products in the order their PKs appear in the list.

**JSON payload:**

```json
{
    "products.pk": [301, 205, 178],
    "default_sorting_deactivated": true
}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJwcm9kdWN0cy5wayI6IFszMDEsIDIwNSwgMTc4XSwgImRlZmF1bHRfc29ydGluZ19kZWFjdGl2YXRlZCI6IHRydWV9'
```

***

### <mark style="color:red;">7. Combining Filter and Exclude</mark>

Both headers can be used together in the same request. In the example below, the result set is narrowed to TR market products and a specific brand is suppressed from those results.

**Filter payload:**

```json
{"products.attributes_available_country": ["tr"]}
```

**Exclude payload:**

```json
{"products.attributes_brand": ["BrandX"]}
```

**curl example:**

```bash
curl --location 'https://{commerce_url}/list/?format=json' \
--header 'X-SEARCH-DYNAMIC-FILTER: eyJwcm9kdWN0cy5hdHRyaWJ1dGVzX2F2YWlsYWJsZV9jb3VudHJ5IjogWyJ0ciJdfQ==' \
--header 'X-SEARCH-DYNAMIC-EXCLUDE: eyJwcm9kdWN0cy5hdHRyaWJ1dGVzX2JyYW5kIjogWyJCcmFuZFgiXX0='
```

## <mark style="color:red;">Field Name Reference</mark>

| Field             | Key format                  | Example                     |
| ----------------- | --------------------------- | --------------------------- |
| Product PK        | `products.pk`               | `[101, 102]`                |
| SKU               | `products.sku.raw`          | `["ABC-001"]`               |
| Base code         | `products.base_code.raw`    | `["BASE-001"]`              |
| Category IDs      | `products.category_ids`     | `[12, 45]`                  |
| Product attribute | `products.attributes_{key}` | `products.attributes_color` |

Product attribute keys come from the `key` field on the Attribute object in Commerce. An attribute must have `is_filterable = true` set before it can be used as a filter or exclude condition.

## <mark style="color:red;">Client Implementation Notes</mark>

The feature is client-agnostic. Any HTTP client that can set request headers can use it.

**Generic pattern (any client):**

1. Build a JSON object with the desired conditions.
2. Encode it with base64.
3. Attach the result as `X-SEARCH-DYNAMIC-FILTER` and/or `X-SEARCH-DYNAMIC-EXCLUDE` on the request to the Commerce `/list/` endpoint.

**omnife BFF — site-wide headers via `GLOBAL_HEADERS_FUNCTION`:**

Implement the function in your omnife project's settings file. It receives the Django `request` object and must return a plain `dict`. Any key-value pairs in that dict are merged into the default headers omnife sends to the Commerce project on every request.

```python
import base64
import json

def global_headers_function(request=None):
    payload = {"products.attributes_available_country": ["tr"]}
    encoded = base64.b64encode(json.dumps(payload).encode()).decode()
    return {"X-Search-Dynamic-Filter": encoded}

GLOBAL_HEADERS_FUNCTION = global_headers_function
```

**omnife BFF — per-request headers (pass-through):**

If the storefront or mobile client already sends the dynamic filter header, omnife passes it through to the Commerce project automatically when `SEARCH_DYNAMIC_FILTER_ACTIVE=True`. No additional configuration is needed on the BFF side for this case.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.akinon.com/tutorials/commerce/how-to-use-dynamic-filter-and-exclude.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
