Symfony UX Pagination
Caution
EXPERIMENTAL This bundle is currently experimental and is likely to change, possibly significantly, before its first stable release.
Symfony UX Pagination provides one lazy, request-aware API for paginating arrays, Doctrine queries and application data sources. It renders accessible links on the server and does not require a browser-side runtime.
The bundle answers the questions that usually leak into controllers and templates:
- Who validates
?page=or?cursor=? - Will the current filters survive the next link?
- Does this screen really need an exact total and its count query?
- Can inserts or deletes move rows while somebody is navigating?
- Can the controls remain ordinary links without a JavaScript lifecycle?
The paginator makes those decisions explicit while keeping one iterable result for PHP, Twig and JSON. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Controller/ProductController.php
namespace App\Controller;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\UX\Pagination\PaginatorInterface;
final class ProductController extends AbstractController
{
#[Route('/products', name: 'product_index')]
public function __invoke(
ProductRepository $repository,
PaginatorInterface $paginator,
): Response {
$products = $paginator
->cursor($repository->createQueryBuilder('product'))
->orderBy(['createdAt', 'id'], 'DESC')
->perPage(20)
->paginate();
return $this->render('product/index.html.twig', [
'products' => $products,
]);
}
}
1 2 3 4 5 6
{# templates/product/index.html.twig #}
{% for product in products %}
<article>{{ product.name }}</article>
{% endfor %}
{{ ux_pagination(products) }}
Mental model
The strategy changes the database contract, not the application architecture:
| Question | Choose | What the result can promise |
|---|---|---|
| Need exact totals/page numbers? | Offset | Counted numbered navigation |
| Only need a reliable next link? | Lookahead | Previous/next, no exact total |
| Can rows change while browsing? | Cursor | Stable sequential traversal |
Start here
- Installation lists the requirements and installation paths.
- Getting started builds a filtered Doctrine list from scratch.
- Choose a pagination strategy helps choose offset, lookahead or cursor pagination.
- Cursor pagination explains stable ordering, signed tokens and backward links.
Use it in an application
- Rendering and customization covers Twig themes, accessibility and browser integration.
- Doctrine ORM and DBAL covers ORM/DBAL queries, counts and database indexes.
- LiveComponent integration adds server-reactive pagination and shows how to keep
the page in a route path such as
/products/{page}. - Integrations covers APIs, a GitHub GraphQL cursor connection, Turbo and third-party UI.
- Custom data-source adapters connects an API, search engine or custom store.
- Adopting UX Pagination compares the bundle with an existing pagination solution.
- Configuration reference is the complete bundle and builder reference.
Ship with confidence
- Debugging and troubleshooting diagnoses request, adapter, count and cursor failures.
- Testing pagination shows unit, functional and LiveComponent test patterns.
- Production, security and performance covers security, performance and operational limits.
The package supports PHP 8.4 or later, Symfony 7.4 or 8.x, and Twig 3.10.3 or later in the Twig 3 series. Doctrine ORM/DBAL, TwigComponent and LiveComponent integrations are optional.