Reusing a Paginated Response Model with OpenAPI components.schemas
Show how to define a reusable pagination envelope in OpenAPI 3.0 using components.schemas and $ref, with a working YAML example, validation commands, and limits to watch for.
23 Aug 2025, 03:24 UTC

Problem: duplicated pagination metadata across endpoints
Many REST APIs return lists wrapped in a common pagination envelope (limit, offset, total, and the items array). Defining that envelope inline for each endpoint leads to repetitive YAML, makes it easy to forget a field, and forces you to edit every copy when the pagination contract changes.
Useful takeaway
Declare the pagination envelope once under components.schemas and reference it with $ref: '#/components/schemas/PaginatedResponse' in any operation that returns a list. A single edit to the envelope updates every endpoint that uses the reference.
How the reference works
The $ref> keyword tells the OpenAPI parser to replace the reference with the target schema located at the given JSON‑Pointer. In OpenAPI 3.0 the pointer must start with #/components/schemas/. The leading # anchors the document root, /components walks to the components object, and /schemas/ selects the specific schema. Omitting any part of that prefix or using a relative path causes the resolver to fail and the spec to be rejected.
Worked example: paginated user list
openapi: 3.0.3
info:
title: User Service API
version: 1.0.0
paths:
/users:
get:
summary: List users with pagination
parameters:
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: offset
in: query
required: false
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: A paginated list of users
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedUserResponse'
components:
schemas:
User:
type: object
required:
- id
- username
properties:
id:
type: integer
username:
type: string
email:
type: string
format: email
PaginatedUserResponse:
type: object
required:
- limit
- offset
- total
- items
properties:
limit:
type: integer
description: Maximum number of items requested
offset:
type: integer
description: Number of items skipped
total:
type: integer
description: Total number of items available
items:
type: array
items:
$ref: '#/components/schemas/User'
The PaginatedUserResponse schema lives once in components.schemas. The /users endpoint references it, so the response model includes limit, offset, total, and an array of User objects.
Verification steps
Validate the spec with the OpenAPI CLI tool:
swagger-cli validate spec.yamlIf the file is valid, the command exits with code 0 and reports no
$refresolution errors.Load the spec in Swagger UI or Redoc and open the
/usersoperation. The “Responses” section should show a model with the four pagination fields and anitemsarray that expands to the User model.Edit the pagination envelope (e.g., add a
nextOffsetfield) in thecomponents.schemas.PaginatedUserResponsedefinition, save, and re‑run the validation command. The UI should now display the new field for every endpoint that references the schema.
Limits and common mistakes
- No true generics: OpenAPI 3.0 cannot express a reusable “PaginatedResponse<T>”. You must create a separate schema for each item type (e.g.,
PaginatedUserResponse,PaginatedOrderResponse). - Circular $ref: A schema that references itself, directly or through another schema, is invalid. Parsers will reject the spec with a “circular reference” error.
- Missing or incorrect prefix: Writing
$ref: 'components/schemas/PaginatedUserResponse'(no leading#) or$ref: '#/components/schema/PaginatedUserResponse'(typo) causes resolution failures. - Relative paths: Using
$ref: './PaginatedUserResponse.yaml'works only when the file is served from a fixed location; moving or bundling the spec breaks the reference. - Mixing OpenAPI versions: Do not combine the
swagger: '2.0'key withopenapi: 3.0.0or use OpenAPI 2.0‑only keywords (e.g.,discriminatorwithout the 3.0 format) in the same document. - Inline overrides: Placing a full schema definition under
responses.200.content.application/json.schemainstead of a$refre‑introduces duplication and defeats the purpose of the components section.
Practical check after changing the envelope
When you modify a component schema, regenerate any client or server stubs that depend on the spec. For example, with OpenAPI Generator:
openapi-generator generate -i spec.yaml -g typescript-fetch -o ./client
Then re‑run swagger-cli validate spec.yaml to confirm the spec is still well‑formed. If the validation passes and the generated code compiles, the change propagated correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.