Filtering and Sorting
The CMS API provides three complementary mechanisms for querying collections: SearchFilter for exact and partial matching on standard fields, MultilingualSearchFilter for searching across multilingual JSON fields, and OrderFilter for sorting results. All three work through query parameters on collection endpoints.
For Product Managers and Integrators
Filtering and sorting let front-end applications display content exactly how users expect it. A blog page can show the newest articles first, a media library can narrow results by file type, and a category picker can search by name in any language — all without custom API endpoints.
Key benefits:
- Consistent behavior across all entities (articles, pages, categories, tags, media, redirects)
- Combinable — filters, search, and sorting can be used together in a single request
- Language-aware — multilingual search finds content regardless of which language it was written in
SearchFilter (Exact and Partial Match)
SearchFilter lets you narrow a collection by matching field values. It supports two match strategies:
- exact — the value must match exactly (useful for statuses, slugs, IDs)
- partial — the value can appear anywhere in the field (useful for filenames, paths)
Syntax
GET /admin/{resource}?{property}={value}
Examples
Filter articles by status:
GET /admin/articles?status=published
Filter articles by category UUID:
GET /admin/articles?category.uuid=019505e5-c5d0-7000-8000-000000000010
Filter media by MIME type:
GET /admin/media?mime_type=image/jpeg
Search media by filename (partial match):
GET /admin/media?original_filename=hero-banner
Filter redirects by status code:
GET /admin/redirects?status_code=301
Filter pages by type:
GET /admin/pages?page_type=landing
Multiple Filters
Combine filters with & to narrow results further. All filters are applied with AND logic:
GET /admin/media?mime_type=image/jpeg&visibility=public&status=ready
MultilingualSearchFilter (Cross-Language Search)
Multilingual fields in the CMS are stored as JSON objects with locale keys (e.g., {"fr": "Titre", "en": "Title"}). The MultilingualSearchFilter searches across all locale values simultaneously, so a single query finds content regardless of which language it was written in.
Syntax
GET /admin/{resource}?{field}={search_term}
How It Works
When you search for "symfony" in the title field:
- The filter extracts all language keys from the JSON column (fr, en, etc.)
- It performs a case-insensitive partial match against each language value
- Results include any resource where at least one language matches
Examples
Search articles by title (matches in any language):
GET /admin/articles?title=symfony
Search articles by excerpt:
GET /admin/articles?excerpt=tutorial
Search categories by name:
GET /admin/categories?name=technology
Search tags by description:
GET /admin/tags?description=framework
MultilingualSearchFilter uses OR logic across languages but AND logic when combined with other filters. A request like ?title=symfony&status=published returns articles where the title matches in any language AND the status is published.
OrderFilter (Sorting)
OrderFilter lets you control the sort order of collection results. Each entity exposes specific sortable properties with a default direction.
Syntax
GET /admin/{resource}?order[{property}]={direction}
Where {direction} is asc or desc.
Examples
Sort articles by newest first:
GET /admin/articles?order[created_at]=desc
Sort media by filename alphabetically:
GET /admin/media?order[original_filename]=asc
Sort redirects by most popular:
GET /admin/redirects?order[hit_count]=desc
Multiple Sort Criteria
Chain sort parameters to define primary and secondary sort order:
GET /admin/articles?order[status]=asc&order[published_at]=desc
This sorts articles by status first (alphabetically), then by publication date within each status group.
Combining Everything
Filters, search, sorting, and pagination work together:
GET /admin/articles?status=published&title=symfony&order[published_at]=desc&page=1&per_page=10
This request:
- Filters to published articles only (SearchFilter)
- Searches for “symfony” in the title across all languages (MultilingualSearchFilter)
- Sorts by publication date, newest first (OrderFilter)
- Returns the first page of 10 results (Pagination)
Entity Filter Reference
The tables below list every available filter property per entity.
Article
| Property | Filter Type | Strategy | Description |
|---|
status | SearchFilter | exact | Filter by content status |
type | SearchFilter | exact | Filter by article type |
tags.slug | SearchFilter | exact | Filter by tag slug |
tags.uuid | SearchFilter | exact | Filter by tag UUID |
category.uuid | SearchFilter | exact | Filter by primary category UUID |
secondaryCategories.uuid | SearchFilter | exact | Filter by secondary category UUID |
title | MultilingualSearch | partial | Search title across all languages |
excerpt | MultilingualSearch | partial | Search excerpt across all languages |
body_html | MultilingualSearch | partial | Search body content across all languages |
created_at | OrderFilter | desc | Sort by creation date |
updated_at | OrderFilter | desc | Sort by last update date |
published_at | OrderFilter | desc | Sort by publication date |
slug | OrderFilter | asc | Sort by slug alphabetically |
status | OrderFilter | asc | Sort by status |
type | OrderFilter | asc | Sort by article type |
Page
| Property | Filter Type | Strategy | Description |
|---|
status | SearchFilter | exact | Filter by content status |
page_type | SearchFilter | exact | Filter by page type |
tags.slug | SearchFilter | exact | Filter by tag slug |
tags.uuid | SearchFilter | exact | Filter by tag UUID |
title | MultilingualSearch | partial | Search title across all languages |
excerpt | MultilingualSearch | partial | Search excerpt across all languages |
body_html | MultilingualSearch | partial | Search body content across all languages |
created_at | OrderFilter | desc | Sort by creation date |
updated_at | OrderFilter | desc | Sort by last update date |
published_at | OrderFilter | desc | Sort by publication date |
slug | OrderFilter | asc | Sort by slug alphabetically |
status | OrderFilter | asc | Sort by status |
page_type | OrderFilter | asc | Sort by page type |
Category
| Property | Filter Type | Strategy | Description |
|---|
slug | SearchFilter | exact | Filter by slug |
name | MultilingualSearch | partial | Search name across all languages |
description | MultilingualSearch | partial | Search description across all languages |
created_at | OrderFilter | desc | Sort by creation date |
slug | OrderFilter | asc | Sort by slug alphabetically |
Tag
| Property | Filter Type | Strategy | Description |
|---|
slug | SearchFilter | exact | Filter by slug |
name | MultilingualSearch | partial | Search name across all languages |
description | MultilingualSearch | partial | Search description across all languages |
created_at | OrderFilter | desc | Sort by creation date |
slug | OrderFilter | asc | Sort by slug alphabetically |
| Property | Filter Type | Strategy | Description |
|---|
mime_type | SearchFilter | exact | Filter by MIME type (e.g., image/jpeg) |
status | SearchFilter | exact | Filter by upload status |
visibility | SearchFilter | exact | Filter by visibility |
original_filename | SearchFilter | partial | Search by original filename |
created_at | OrderFilter | desc | Sort by creation date |
updated_at | OrderFilter | desc | Sort by last update date |
original_filename | OrderFilter | asc | Sort by filename alphabetically |
mime_type | OrderFilter | asc | Sort by MIME type |
size | OrderFilter | desc | Sort by file size |
Redirect
| Property | Filter Type | Strategy | Description |
|---|
source_path | SearchFilter | partial | Search by source path |
target_path | SearchFilter | partial | Search by target path |
status | SearchFilter | exact | Filter by redirect status |
status_code | SearchFilter | exact | Filter by HTTP status code (301, 302) |
created_at | OrderFilter | desc | Sort by creation date |
hit_count | OrderFilter | desc | Sort by number of hits |
source_path | OrderFilter | asc | Sort by source path alphabetically |
status_code | OrderFilter | asc | Sort by status code |
Technical Details
How Filters Are Implemented
Filters are declared as PHP attributes on entity classes using API Platform’s #[ApiFilter] attribute:
#[ApiFilter(SearchFilter::class, properties: [
'status' => 'exact',
'mime_type' => 'exact',
'original_filename' => 'partial',
])]
#[ApiFilter(OrderFilter::class, properties: [
'created_at' => 'DESC',
'updated_at' => 'DESC',
])]
The properties array maps field names to their default behavior:
- For SearchFilter: the value is the match strategy (
exact or partial)
- For OrderFilter: the value is the default sort direction (
ASC or DESC), which clients can override in query parameters
MultilingualSearchFilter Internals
The custom MultilingualSearchFilter extends API Platform’s AbstractFilter. It queries PostgreSQL JSON columns using CAST and LIKE operators to search across all locale values stored in a JSON field.
When no properties are configured (as on Article and Page), it defaults to searching title, excerpt, and body_html. When properties are explicitly set (as on Category and Tag), it uses only those fields:
// Defaults to title, excerpt, body_html
#[ApiFilter(MultilingualSearchFilter::class)]
// Searches only name and description
#[ApiFilter(MultilingualSearchFilter::class, properties: ['name' => 'partial', 'description' => 'partial'])]
Default Sort Direction
The direction specified in the OrderFilter configuration is the default when a client does not explicitly provide one. Clients can always override it:
# Uses default (DESC for created_at)
GET /admin/articles?order[created_at]
# Explicitly overrides to ascending
GET /admin/articles?order[created_at]=asc
OpenAPI Integration
All filters are automatically documented in the OpenAPI specification. The /openapi.json endpoint includes filter parameters for every collection operation, making them discoverable in tools like Swagger UI and Postman.