Skip to main content
Version: 2026.3

Filtering and Paging

Fulltext search and simple filtering​

Use the fulltext_search field for fulltext search and simple filtering. It supports simple_query_string capabilities of OpenSearch/Elasticsearch and the following operators:

  • + signifies AND operation
  • | signifies OR operation
  • - negates a single term
  • " wraps a number of term to signify a phrase for searching
  • * at the end of a term signifies a prefix query
  • ( and ) signify precedence
  • ~n used after a term (for example, wnid~3), sets fuzziness, when used after a phrase, sets slop.

Details see

Use the query_string_query field for advanced filtering. It supports query_string capabilities and syntax of OpenSearch/Elasticsearch which allows advanced filtering for specific fields.

Details see

Query Filters (legacy filtering mechanism)​

Additionally, querying can be done using a limited and simplified subset of the syntax described here.

Supported Logic Operators​

  • $not
  • $or
  • $and

Examples​

filter={"system.modificationDate" : "1729685234"}

... SQL equivalent ...
where `modificationDate` = '1729685234'
filter=[{"system.creationDate" : "1557394706"}, {"system.modificationDate" : "1729685234"}]
...
where `creationDate` = '1557394706' AND `modificationDate` = '1557394706'
filter={"$and" : [{"system.subtype" : "Car"}, {"$or": [{"system.id": "14"}, {"system.key": "E-Type"}]}]}
...
where `subtype` = 'Car' AND (`id` = '14' OR `key` = 'E-Type')
filter={"$and" : [{"system.subtype": "Car"}, {"data.Manufacturer" : "Jaguar"}, {"$or": [{"system.id": "14"}, {"system.key": "E-Type"}]}]}
...
where `subtype` = 'Car' AND Manufacturer = 'Jaguar' AND (`id` = '14' OR `key` = 'E-Type')
filter={"system.type": {"$not": "folder"}}
...
where NOT `type` = 'folder'

Sorting​

Use the order_by parameter to control result ordering.

Format​

Simple string - field name only (defaults to ascending):

"fieldPath"

JSON object - field paths as keys and sort direction as values:

{"fieldPath": "asc|desc"}

JSON object with options (needed for Elasticsearch only) - extended format with sort direction and unmapped_type:

{"fieldPath": {"order": "asc|desc", "unmapped_type": "keyword|double"}}

JSON array - multiple fields (first is primary sort, subsequent are tiebreakers):

{"data.priority": "desc", "system.creationDate": "asc"}

Examples​

Sort by modification date (simple string, ascending):

system.modificationDate

Sort by modification date (newest first):

{"system.modificationDate": "desc"}

Sort by price (lowest first):

{"data.price": "asc"}

Sort by price with explicit unmapped_type (for numeric fields in Elasticsearch):

{"data.price": {"order": "asc", "unmapped_type": "double"}}

Multiple sort fields:

{"data.category": "asc", "data.price": "desc", "system.id": "asc"}

Mixed format (simple and extended):

{"data.price": {"order": "asc", "unmapped_type": "double"}, "system.id": "asc"}

The unmapped_type Parameter​

When querying across multiple indices where a sort field may not exist in all indices, you can specify unmapped_type to define how missing fields should be treated.

When to use:

  • Elasticsearch: Required when sorting by numeric fields (e.g., data.price, data.number) to prevent "incompatible sort types" errors
  • OpenSearch: Optional

Supported types:

  • keyword - For text/string fields (default)
  • double - For numeric fields

Why it matters: When a field exists in some indices but not others (e.g., data.price exists in product objects but not in asset or folder indices), Elasticsearch needs to know how to handle the missing field during sorting. Without unmapped_type, queries may fail with "incompatible sort types" errors when the same field name has different types across shards.

Examples:

// Numeric field sorting (Elasticsearch)
{"data.number": {"order": "asc", "unmapped_type": "double"}}

// Text field sorting
{"data.name": {"order": "asc", "unmapped_type": "keyword"}}

// Multiple fields with mixed types
{
"data.priority": {"order": "desc", "unmapped_type": "double"},
"data.name": {"order": "asc", "unmapped_type": "keyword"},
"system.id": "asc"
}

Default Behavior​

Every result is sorted, and every sort ends in a system.id, system.type tiebreaker that is added automatically. If order_by is omitted, results are ordered by relevance score when the request has a fulltext_search, and by system.id ascending otherwise (query_string_query and filter do not score). A deterministic order is what keeps paging from skipping or repeating records between requests.

Paging​

The paging can be done via the page_cursor parameter in the query. When loading the next page of a result, use the value provided by the previous response for the page_cursor parameter. The value is available in the link header or via the data attribute page_cursor in the response content. A page_cursor of null means the response was the last page: the server asks the index for one item beyond the page, so the signal reflects the result set as it is now (not a count taken earlier), and the link header's rel="next" is only sent when there is a next page.

Technical Details​

The page_cursor has two operation modes:

  • for pages below the configured max results window, it uses a numeric value and OpenSearch from query.
  • from the max results window on, it is an opaque token that uses the search_after option of OpenSearch or Elasticsearch respectively, and paging continues to the end of the result set with or without order_by.

Always pass the cursor back exactly as received. A numeric page_cursor that, together with the page size, reaches beyond the max results window is rejected with HTTP 400 (the cursor a response returns never addresses such an offset; it has already switched to the opaque form by then).

The max results window can be configured via symfony configuration:

pimcore_data_hub_simple_rest:
# Limit of page size and offset when paging only works via page cursor (and not page numbers anymore).
max_results_window: 10000