Offering Filtering and Sorting in the GraphQL Query
Offering Filtering and Sorting in the GraphQL Query
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
The events query from chapter 13 currently always returns every active event in insertion order. This chapter adds a filter and a sort parameter, both as custom input types (chapter 6), deliberately reusing the existing core building blocks FilterTypeInput and SortEnum.
Declaring the filter input type
input EventFilterInput @doc(description: "Identifies which fields to filter events by") {
title: FilterTypeInput
location: FilterTypeInput
}
input EventSortInput @doc(description: "Specifies which fields to sort events by") {
title: SortEnum
start_at: SortEnum
}FilterTypeInput is the same core type that already showed up in chapter 3 in product search - it brings operators such as eq, like, in along, without the events module having to redefine them. SortEnum is a plain enum with the values ASC and DESC, also from Magento_CatalogGraphQl.
Adding the arguments to the query
type Query {
events(
filter: EventFilterInput
sort: EventSortInput
pageSize: Int = 20
currentPage: Int = 1
): Events
@resolver(class: "Mironsoft\\Event\\Model\\Resolver\\Events")
@doc(description: "Returns a filtered, sorted, paginated list of active events")
}Extending the DataProvider
The getList() signature from chapter 13 gets two additional, optional parameters. Both are translated into filters or sort orders through the already-existing SearchCriteriaBuilder:
/**
* Fetches active events for the given page, optionally filtered and sorted.
*
* @param int $pageSize Number of events per page
* @param int $currentPage Requested page, 1-based
* @param array<string, array<string, string>>|null $filter Field => [operator => value]
* @param array<string, string>|null $sort Field => direction (ASC/DESC)
* @return array{items: array<int, array<string, mixed>>, total_count: int, page_info: array<string, int>}
*/
public function getList(
int $pageSize,
int $currentPage,
?array $filter = null,
?array $sort = null
): array {
$this->searchCriteriaBuilder->addFilter(EventInterface::IS_ACTIVE, 1);
foreach ($filter ?? [] as $field => $operatorValuePairs) {
foreach ($operatorValuePairs as $operator => $value) {
$this->searchCriteriaBuilder->addFilter($field, $value, $operator);
}
}
$this->searchCriteriaBuilder->setCurrentPage($currentPage);
$this->searchCriteriaBuilder->setPageSize($pageSize);
$searchCriteria = $this->searchCriteriaBuilder->create();
foreach ($sort ?? [] as $field => $direction) {
$sortOrder = $this->sortOrderBuilder->setField($field)
->setDirection($direction)
->create();
$searchCriteria->setSortOrders([$sortOrder]);
}
// ... rest as in chapter 13: call the repository, map items, build page_info
}$sortOrderBuilder (\Magento\Framework\Api\SortOrderBuilder) gets added as another constructor dependency - consistently via constructor property promotion, like every other dependency in this project.
Adjusting the resolver
$filter = $args['filter'] ?? null;
$sort = $args['sort'] ?? null;
return $this->eventsDataProvider->getList(
(int) ($args['pageSize'] ?? 20),
(int) ($args['currentPage'] ?? 1),
$filter,
$sort
);The extended query in practice
query {
events(
filter: { location: { eq: "Berlin" } }
sort: { start_at: ASC }
pageSize: 10
) {
items { title location start_at }
total_count
}
}Achtung: $args['filter'] arrives from GraphQL as a nested array with the operator as the key (['location' => ['eq' => 'Berlin']]) - almost identical to what addFieldToFilter() itself expects as its second parameter. This structural similarity is no accident: FilterTypeInput was deliberately modeled so it translates directly into Magento's collection filter API.
Tipp: When several filter fields are set at once, SearchCriteriaBuilder::addFilter() combines them with AND by default - OR combinations would need a shared filter group via addFilters() with multiple filters in the same array. For most storefront filters (like here), the simple AND combination is entirely sufficient.
Chapter 15 wraps up block 4: querying a single event by identifier, instead of always loading the entire (filtered) list.