Skip to content

Nested Filters

NestedFilterCondition enables filtering on nested document fields — that is, arrays of objects stored inside an Elasticsearch document (e.g. order lines, delivery contacts, shipment events). Nested filtering applies the inner filter to each element of the nested array independently, preventing cross-object field mixing.


Type Definition

graphql
input FilterConditionInput {
  nested: NestedFilterCondition
}

input NestedFilterCondition {
  path:   String!              # Dot-notation path to the nested field
  filter: ComplexFilterInput!  # Filter to apply within the nested object
}

The filter inside NestedFilterCondition is a full ComplexFilterInput, so you can combine must, mustNot, and should clauses within the nested scope, and even nest further.


Supported Nested Paths

DomainPathFilterable Fields
Orderslineslines.sku, lines.status, lines.quantity
OrdersdeliveryContactdeliveryContact.country, deliveryContact.city
Orderstagstags.value
OrdersdeliveryOrdersdeliveryOrders.status, deliveryOrders.type
OrdersdeliveryOrders.tagsdeliveryOrders.tags.level
Shipmentseventsevents.event, events.country
Delivery Orderslineslines.sku, lines.status

Field names inside the nested filter must be fully qualified with the path prefix (e.g. lines.sku, not just sku).


Examples

Filter orders containing a specific SKU

graphql
complexFilters: {
  must: [
    {
      nested: {
        path: "lines",
        filter: {
          must: [
            { property: "lines.sku", operator: in, values: ["TSHIRT-S-BLUE"] }
          ]
        }
      }
    }
  ]
}

Filter orders delivered to France

graphql
complexFilters: {
  must: [
    {
      nested: {
        path: "deliveryContact",
        filter: {
          must: [
            { property: "deliveryContact.country", operator: in, values: ["FR"] }
          ]
        }
      }
    }
  ]
}

Filter orders with delivered shipments

graphql
complexFilters: {
  must: [
    {
      nested: {
        path: "shipments",
        filter: {
          must: [
            { property: "shipments.lastEvent", operator: in, values: ["DELIVERED"] }
          ]
        }
      }
    }
  ]
}

Multi-level filter — OPENED orders shipping to France

Combine a top-level facet filter with a nested filter:

graphql
complexFilters: {
  must: [
    { property: "organizationId", operator: in, values: ["org-abc123"] },
    { property: "state", operator: in, values: ["OPENED"] },
    {
      nested: {
        path: "deliveryContact",
        filter: {
          must: [
            { property: "deliveryContact.country", operator: in, values: ["FR"] }
          ]
        }
      }
    }
  ]
}

Multi-level nested — orders with delivery orders that have WARNING or ERROR tags

Nesting inside a nested path:

graphql
complexFilters: {
  must: [
    { property: "organizationId", operator: in, values: ["org-abc123"] },
    {
      nested: {
        path: "deliveryOrders",
        filter: {
          must: [
            {
              nested: {
                path: "deliveryOrders.tags",
                filter: {
                  must: [
                    {
                      property: "deliveryOrders.tags.level",
                      operator: in,
                      values: ["WARNING", "ERROR"]
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  ]
}

Filter orders with a specific SKU going to a specific country

Combining two independent nested conditions:

graphql
complexFilters: {
  must: [
    { property: "organizationId", operator: in, values: ["org-abc123"] },
    {
      nested: {
        path: "lines",
        filter: {
          must: [
            { property: "lines.sku", operator: in, values: ["TSHIRT-S-BLUE"] }
          ]
        }
      }
    },
    {
      nested: {
        path: "deliveryContact",
        filter: {
          must: [
            { property: "deliveryContact.country", operator: in, values: ["FR"] }
          ]
        }
      }
    }
  ]
}

Full Example — Filter Orders by SKU and Destination Country

bash
curl -X POST https://api-v3.happycolis.com/graphql \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query SearchOrders($input: SearchInput!) { orderRecords(input: $input) { hits { objectID invoiceNumber state } nbHits } }",
    "variables": {
      "input": {
        "complexFilters": {
          "must": [
            { "property": "organizationId", "operator": "in", "values": ["org-abc123"] },
            {
              "nested": {
                "path": "lines",
                "filter": {
                  "must": [
                    { "property": "lines.sku", "operator": "in", "values": ["TSHIRT-S-BLUE"] }
                  ]
                }
              }
            },
            {
              "nested": {
                "path": "deliveryContact",
                "filter": {
                  "must": [
                    { "property": "deliveryContact.country", "operator": "in", "values": ["FR"] }
                  ]
                }
              }
            }
          ]
        },
        "hitsPerPage": 50,
        "page": 0
      }
    }
  }'
javascript
const response = await fetch('https://api-v3.happycolis.com/graphql', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `query SearchOrders($input: SearchInput!) {
      orderRecords(input: $input) {
        hits { objectID invoiceNumber state }
        nbHits
      }
    }`,
    variables: {
      input: {
        complexFilters: {
          must: [
            { property: 'organizationId', operator: 'in', values: ['org-abc123'] },
            {
              nested: {
                path: 'lines',
                filter: {
                  must: [
                    { property: 'lines.sku', operator: 'in', values: ['TSHIRT-S-BLUE'] },
                  ],
                },
              },
            },
            {
              nested: {
                path: 'deliveryContact',
                filter: {
                  must: [
                    { property: 'deliveryContact.country', operator: 'in', values: ['FR'] },
                  ],
                },
              },
            },
          ],
        },
        hitsPerPage: 50,
        page: 0,
      },
    },
  }),
});
const { data } = await response.json();
python
import requests

response = requests.post(
    'https://api-v3.happycolis.com/graphql',
    headers={
        'Authorization': f'Bearer {ACCESS_TOKEN}',
        'Content-Type': 'application/json',
    },
    json={
        'query': '''
            query SearchOrders($input: SearchInput!) {
                orderRecords(input: $input) {
                    hits { objectID invoiceNumber state }
                    nbHits
                }
            }
        ''',
        'variables': {
            'input': {
                'complexFilters': {
                    'must': [
                        {'property': 'organizationId', 'operator': 'in', 'values': ['org-abc123']},
                        {
                            'nested': {
                                'path': 'lines',
                                'filter': {
                                    'must': [
                                        {'property': 'lines.sku', 'operator': 'in', 'values': ['TSHIRT-S-BLUE']},
                                    ],
                                },
                            },
                        },
                        {
                            'nested': {
                                'path': 'deliveryContact',
                                'filter': {
                                    'must': [
                                        {'property': 'deliveryContact.country', 'operator': 'in', 'values': ['FR']},
                                    ],
                                },
                            },
                        },
                    ],
                },
                'hitsPerPage': 50,
                'page': 0,
            }
        },
    }
)
data = response.json()
php
<?php
$client = new \GuzzleHttp\Client();
$response = $client->post('https://api-v3.happycolis.com/graphql', [
    'headers' => [
        'Authorization' => 'Bearer ' . $ACCESS_TOKEN,
        'Content-Type'  => 'application/json',
    ],
    'json' => [
        'query' => 'query SearchOrders($input: SearchInput!) {
            orderRecords(input: $input) {
                hits { objectID invoiceNumber state }
                nbHits
            }
        }',
        'variables' => [
            'input' => [
                'complexFilters' => [
                    'must' => [
                        ['property' => 'organizationId', 'operator' => 'in', 'values' => ['org-abc123']],
                        [
                            'nested' => [
                                'path' => 'lines',
                                'filter' => [
                                    'must' => [
                                        ['property' => 'lines.sku', 'operator' => 'in', 'values' => ['TSHIRT-S-BLUE']],
                                    ],
                                ],
                            ],
                        ],
                        [
                            'nested' => [
                                'path' => 'deliveryContact',
                                'filter' => [
                                    'must' => [
                                        ['property' => 'deliveryContact.country', 'operator' => 'in', 'values' => ['FR']],
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
                'hitsPerPage' => 50,
                'page' => 0,
            ],
        ],
    ],
]);
$data = json_decode($response->getBody(), true);
go
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

payload, _ := json.Marshal(map[string]interface{}{
    "query": `query SearchOrders($input: SearchInput!) {
        orderRecords(input: $input) {
            hits { objectID invoiceNumber state }
            nbHits
        }
    }`,
    "variables": map[string]interface{}{
        "input": map[string]interface{}{
            "complexFilters": map[string]interface{}{
                "must": []map[string]interface{}{
                    {"property": "organizationId", "operator": "in", "values": []string{"org-abc123"}},
                    {
                        "nested": map[string]interface{}{
                            "path": "lines",
                            "filter": map[string]interface{}{
                                "must": []map[string]interface{}{
                                    {"property": "lines.sku", "operator": "in", "values": []string{"TSHIRT-S-BLUE"}},
                                },
                            },
                        },
                    },
                    {
                        "nested": map[string]interface{}{
                            "path": "deliveryContact",
                            "filter": map[string]interface{}{
                                "must": []map[string]interface{}{
                                    {"property": "deliveryContact.country", "operator": "in", "values": []string{"FR"}},
                                },
                            },
                        },
                    },
                },
            },
            "hitsPerPage": 50,
            "page":        0,
        },
    },
})
req, _ := http.NewRequest("POST", "https://api-v3.happycolis.com/graphql", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+ACCESS_TOKEN)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)

Important Notes

  • Field names inside a nested filter must include the full path prefix (e.g. lines.sku, not sku).
  • Each nested condition independently scopes the filter to matching elements of the nested array, preventing cross-object matching (e.g. a filter on lines.sku = A AND lines.quantity > 5 ensures both conditions apply to the same line, not different lines).
  • Nesting depth is supported (e.g. deliveryOrdersdeliveryOrders.tags), but each level requires its own nested wrapper.

HappyColis API Documentation