agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/api/pagination/SKILL.md

pagination

Skill4 starsChanged 4 months agoMITView on GitHub

Page large API collections predictably — choose offset versus cursor pagination, expose navigation links, and enforce sane page-size limits so responses stay bounded and stable under concurrent writes. Use when the user needs to expose next/previous page navigation and page-size limits in an API response; the user must choose between offset/limit and cursor-based pagination for an API collection; the user needs to return a large collection from an API in pages rather than all at once. Keywords: api, pagination, cursor, offset, limits, links.

3 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

3 rules. 0 are common, 3 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not page a full bulk export where the client wants every row.

    line 63
  2. Distinctive:

    Do not paginate a collection that is inherently small and bounded, such as a fixed list of a user's roles or a handful of settings.

    line 64
  3. Distinctive:

    Do not compute an exact total count on every page request

    line 88

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- generated: do not edit — source: knowledge/atoms/api/pagination/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

API Pagination

When to use

  • The user needs to expose next/previous page navigation and page-size limits in an API response.
  • The user must choose between offset/limit and cursor-based pagination for an API collection.
  • The user needs to return a large collection from an API in pages rather than all at once.

When NOT to use

  • Do not page a full bulk export where the client wants every row; stream the response or provide an async export job instead.
  • Do not paginate a collection that is inherently small and bounded, such as a fixed list of a user's roles or a handful of settings.

Core guidance

  • Prefer cursor pagination for deep or fast-changing data — Use cursor (keyset) pagination — a cursor pointing at the last seen sort key — for large, deep, or frequently changing collections. Reserve offset/limit for small datasets or when clients need to jump to an arbitrary page number.
  • Enforce a default and maximum page size — Apply a sensible default page size (for example 20) and clamp the client-supplied size to a hard maximum (for example 100). Reject or cap oversized requests rather than honoring an unbounded per_page.

Quick recipe

  1. Choose a stable, unique, indexed sort key (for example id or created_at, id), read the requested page size, and clamp it to the server maximum. (verify: The chosen key is indexed and unique, and an oversized page-size request is capped.)
  2. Decode the incoming cursor to the last seen key value and query WHERE (sort_key) > :cursor ORDER BY sort_key LIMIT :size + 1, fetching one extra row to detect whether more pages exist. (verify: The query seeks by the indexed key and the extra row correctly indicates has_more.)
  3. Return the page items plus a consistent meta block — the next cursor (encoded from the last item), a has_more flag, and a ready-to-follow next link. (verify: Following the returned next link fetches the subsequent page with no gaps or duplicates.)

Best practices

  • Do not compute an exact total count on every page request
  • Include ready-to-follow next (and prev/first where meaningful) links or the next cursor in the response, so clients follow links rather than constructing page URLs themselves
  • Order pages by a stable, unique key (or a composite ending in the primary key) that is indexed, and derive the cursor from it

Anti-patterns

Anti-patternWhy it failsDo instead
Running an exact total COUNT on every pageThe COUNT over a large filtered set can cost as much as the page query, doubling load for a number most clients never use.Omit the total, cache it, or return a has_more flag; compute an exact count only when the client explicitly asks.
A different pagination envelope per endpointClients must special-case every route, and shared pagination helpers become impossible to reuse.Standardize one envelope (data plus a consistent meta/links block) and apply it across all paginated endpoints.
Calling an offset page number a cursorIt inherits every offset weakness (drift, deep-page cost) while implying keyset guarantees the client does not actually get.Base a real cursor on the last row's stable sort-key value so paging seeks by index, not by counting rows.

See references/anti-patterns.md.

Common mistakes

  1. Deep offsets get progressively slower — A query like LIMIT 20 OFFSET 100000 still scans and discards the first 100000 rows before returning the page. Fix: Switch deep pagination to keyset/cursor so each page seeks directly to the next range via an index.
  2. Offset pagination skips or repeats rows under writes — With offset/limit, a row inserted or deleted between page requests shifts the window, so clients skip or see duplicate items. Fix: Use cursor (keyset) pagination anchored on a stable key so the next page continues from the last seen item regardless of inserts.
  3. Honoring an unbounded client page size — Trusting the client's per_page without a cap lets ?per_page=1000000 pull the whole table in one response. Fix: Clamp the requested size to a hard maximum server-side and document the ceiling.

Version notes

  • The Link header format is standardized by RFC 8288 (PHP >=8.1) — When returning pagination links in the HTTP Link header, use the RFC 8288 web linking format with rel="next" and rel="prev"; the same relations also work inside a JSON links block.

Security

  • Keep cursors opaque and scoped to the caller — Validate the decoded cursor as untrusted input and re-apply the caller's authorization filter (tenant, owner) to every page, so a forged or swapped cursor cannot page into another user's records or crash the decoder.

Testing

  • Activation and decision behavior are verified in eval/scenarios/api/pagination.yaml.

Additional resources

  • references/anti-patterns.md
  • references/core.md
  • references/examples.md
  • references/pitfalls.md
  • references/recipes.md
  • references/version-differences.md

Knowledge graph

Related: api/openapi, api/rest-design, security/sql-injection-prevention

<!-- graph-fragment: graph/fragments/api/pagination.yaml -->

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.