---
title: "Sorting"
manual: "TypoGraph"
permalink: "https://docs.typo3.org/permalink/digicademy/typograph:sorting"
source: "sorting/index.rst"
rendered: "2026-09-18T09:10:56+00:00"
---

# Sorting {#sorting-1}

-   [How It Works](https://docs.typo3.org/permalink/digicademy/typograph:how-it-works)
-   [Sorting Behaviour](https://docs.typo3.org/permalink/digicademy/typograph:sorting-behaviour)
-   [Using the sortBy Argument](https://docs.typo3.org/permalink/digicademy/typograph:using-the-sortby-argument)
-   [Implementing a Custom Comparator](https://docs.typo3.org/permalink/digicademy/typograph:implementing-a-custom-comparator)
-   [Complete Example](https://docs.typo3.org/permalink/digicademy/typograph:complete-example)

TypoGraph supports custom sorting of query results via the
`ComparatorInterface`. This allows external packages to inject
locale-aware or domain-specific sorting logic without modifying
TypoGraph itself.

## How It Works {#how-it-works}

Sorting is based on two components:

1.  A **ComparatorInterface** implementation injected via dependency injection.
    This defines *how* strings are compared (e.g. locale-aware collation).
1.  A **sortBy argument** on the GraphQL query that specifies *which field*
    to sort by.

When both are present, TypoGraph applies the comparator to sort result sets.
When either is absent, results are returned in their default order (UID for
connection queries, database default for plain lists).

## Sorting Behaviour {#sorting-behaviour}

Sorting behaviour differs depending on the query type:

-   ****Plain list queries** (e.g. `[Expert]`)**

    The full result set is sorted by the comparator before being returned.

-   ****Connection queries** (e.g. `ExpertConnection`)**

    Cursor-based pagination relies on UID ordering for stable pagination across
    pages. The comparator sorts records *within the current page only* after the
    database query and pagination slicing. This means the overall page boundaries
    are determined by UID order, but the records within each page are reordered
    by the comparator.

## Using the `sortBy` Argument {#using-the-sortby-argument}

Clients specify which field to sort by using the `sortBy` argument on a
query field. To enable this, add `sortBy: String` to the relevant query
fields in your GraphQL schema:

**Schema with sortBy argument**

```text
type Query {
  experts(sortBy: String): [Expert]
  expertsConnection(sortBy: String, first: Int, after: String): ExpertConnection
}
```

The `sortBy` value should be the **camelCase GraphQL field name** (e.g.
`familyName`). TypoGraph converts it to the snake_case database column
name automatically.

**Query with sort field**

```text
{
  experts(sortBy: "familyName") {
    familyName
    givenName
  }
}
```

When `sortBy` is omitted, no comparator-based sorting is applied and
results are returned in their default database order.

**Nonexistent fields:** If the `sortBy` value refers to a field that does
not exist in the result records, sorting is silently skipped and the original
order is preserved.

## Implementing a Custom Comparator {#implementing-a-custom-comparator}

TypoGraph ships a `ComparatorInterface` with a single method:

**EXT:typograph/Classes/Comparator/ComparatorInterface.php**

```php
namespace Digicademy\TypoGraph\Comparator;

interface ComparatorInterface
{
    public function compare(string $a, string $b): int;
}
```

The method follows PHP's standard comparison contract: return a negative
integer if `$a < $b`, zero if equal, or a positive integer if `$a > $b`.

To provide a custom comparator, create a class implementing this interface
in your sitepackage or extension:

**EXT:my_sitepackage/Classes/Comparator/LocalizedComparator.php**

```php
namespace Vendor\MySitepackage\Comparator;

use Digicademy\TypoGraph\Comparator\ComparatorInterface;

class LocalizedComparator implements ComparatorInterface
{
    private static array $collators = [];

    public function compare(string $a, string $b): int
    {
        if (!isset(self::$collators['de_DE'])) {
            self::$collators['de_DE'] = \Collator::create('de_DE');
        }

        return self::$collators['de_DE']->compare($a, $b);
    }
}
```

Then register your implementation as the `ComparatorInterface` service in
your extension's `Services.yaml`:

**EXT:my_sitepackage/Configuration/Services.yaml**

```yaml
services:
  Digicademy\TypoGraph\Comparator\ComparatorInterface:
    class: Vendor\MySitepackage\Comparator\LocalizedComparator
```

TypoGraph's `ResolverService` accepts the comparator as an optional
constructor dependency. When no implementation is registered, the parameter
defaults to `null` and no custom sorting is applied.

## Complete Example {#complete-example}

Given this schema and configuration:

**Schema**

```text
type Query {
  experts(sortBy: String): [Expert]
  expertsConnection(sortBy: String, first: Int, after: String): ExpertConnection
}

type Expert {
  familyName: String
  givenName: String
}
```

A plain list query sorted by family name:

**Query sorted by familyName**

```text
{
  experts(sortBy: "familyName") {
    familyName
    givenName
  }
}
```

The same type sorted by a different field:

**Query sorted by givenName**

```text
{
  experts(sortBy: "givenName") {
    familyName
    givenName
  }
}
```

A connection query sorts records within each page:

**Paginated query with sorting**

```text
{
  expertsConnection(sortBy: "familyName", first: 10) {
    edges {
      node {
        familyName
        givenName
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```
