Skip to content

feat(state): RFC 8288 Link relations for paginated collections - #8541

Open
Nayte91 wants to merge 3 commits into
api-platform:mainfrom
Nayte91:feat/RFC-8288-links
Open

Nayte91 wants to merge 3 commits into
api-platform:mainfrom
Nayte91:feat/RFC-8288-links

Conversation

@Nayte91

@Nayte91 Nayte91 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? main
Tickets related to #7137 (the pagination-in-headers thread); independent of #7856
License MIT
Doc PR api-platform/docs#TODO

2nd step of my issue #7137, as "How to handle pagination, the stanrdized way?". After #7856 that adds Content-Range and Request-Range, we now add a RFCed header that gives link to collection pages, that allow to know how much page do we have in this current collection, giving a nice approximation of the number of items.

Topic

A collection operation that opts in with paginationLinkHeader: true answers with the pagination relations of
RFC 8288 (Web Linking) in the Link response header:

GET /books?page=2

HTTP/1.1 200 OK
Link: </books?page=2>; rel="self",</books?page=1>; rel="first",</books?page=1>; rel="prev",</books?page=3>; rel="next",</books?page=12>; rel="last"

The five relation types are registered at IANA. A HEAD request carries the same header with an empty body
(RFC 9110 §9.3.2), so a client can learn the page count and its own position without parsing a representation,
whatever the format.

Design choices

  • Opt-in. Nothing changes for an operation that does not declare the flag. It sits on HttpOperation and on ApiResource like the other pagination* flags, so it propagates to the operations and is a validated api_platform.defaults key.
  • The hrefs are the request's own query, with page recomputed. No parameter is injected: the header links are the same URLs as the hydra:view body links, so a page has one URL and one cache key.
  • A single-page collection still gets first and last. The body view emits nothing in that case; a HEAD client has no other way to learn that the collection holds one page.
  • No total count. No standard header carries a collection's cardinality; clients derive the page count from last.
  • Cursor-based pagination emits nothing. Its next/prev depend on the served items, not on a page number; left for a later change.
  • Link-Template is out of scope: feat: Link-Template (RFC 9652), link sets (RFC 9264) and api-catalog (RFC 9727) #8468 brings the RFC 9652 transport, and a templated pagination link can be added on top of it afterwards.

Implementation

ApiPlatform\State\Processor\PaginationLinkProcessor decorates api_platform.state_processor.respond (priority
420, inside the Hydra link processor) and pushes Symfony\Component\WebLink\Link objects into the
_api_platform_links provider; the existing AddLinkHeaderProcessor serializes them, so the Hydra
apiDocumentation entry and the pagination entries share one header line. The page arithmetic mirrors
Hydra\State\Util\PaginationHelperTrait (the State component cannot depend on Hydra; a marker points at the
duplication). Wired once for both Symfony modes (state/pagination_link.php, loaded unconditionally) and in the
Laravel service provider at the same depth of the respond chain.

Tests

  • unit: guards, relation order, request query preserved, original_data precedence;
  • functional, default and use_symfony_listeners modes: page 1 / middle / last / single page, HEAD, client page size kept or echoed, filters preserved, partial paginator, ABS_URL strategy, opt-out;
  • acceptance: a client that reads the pagination state from the headers only, through Symfony's HttpHeaderParser, and checks that HEAD and GET yield the same state.

Notes

  • Browser clients need Access-Control-Expose-Headers: Link from the deployer's CORS configuration.
  • On collections with long filter query strings, five hrefs can make the header large; relative URIs keep it small, absolute ones (urlGenerationStrategy) do not.

Happy to flip the default to enabled, or to narrow the flag to HttpOperation only, if you prefer.

@Nayte91
Nayte91 force-pushed the feat/RFC-8288-links branch from 3d40d7f to 94234b5 Compare September 17, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant