skip to content

In an OpenAPI document, what does a $ref such as '#/components/schemas/Pet' resolve to?

level: juniorimportance: must knowfreq 70%

answer

  1. A pointer, not an import
  2. Everything after # walks the document
  3. components is storage, not surface
  4. #/components/schemas/<Name>
  5. Unreferenced definitions describe nothing

basics

~10 s

It is a JSON Pointer into the same document: it resolves to the Pet entry under components.schemas, and tooling substitutes that definition wherever the reference appears, so one definition can serve many operations.

solid answer

~40 s

`$ref` is OpenAPI's reference mechanism. The value is a URI; when it starts with `#`, the rest is a **JSON Pointer** navigating the current document, so `#/components/schemas/Pet` walks to the `components` object, then `schemas`, then the key `Pet`. Any tool reading the spec — validator, docs renderer, code generator — substitutes the referenced object at that position. The `components` object is a registry, not part of the API surface: nothing under it is exposed just by being defined there, it only matters once something references it. Component keys are restricted to letters, digits, dot, dash and underscore. In OpenAPI 3.0 a Reference Object may contain only `$ref` — sibling keys such as `description` are ignored — which surprises people who try to annotate a reference in place.

code

yaml · 50 lines
yaml
openapi: 3.0.3
info:
  title: Pet Store
  version: 1.0.0
paths:
  /pets/{petId}:
    get:
      operationId: getPet
      parameters:
        - $ref: '#/components/parameters/PetId'
      responses:
        '200':
          description: The pet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  parameters:
    PetId:
      name: petId
      in: path
      required: true
      schema:
        type: string
  schemas:
    Pet:
      type: object
      required: [id, name]
      properties:
        id:
          type: string
        name:
          type: string
    Error:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
  responses:
    NotFound:
      description: Resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

go deeper

for a junior

Be ready to read a pointer out loud and say where it lands: #/components/schemas/Order means the Order entry under components.schemas in this same file.

for a middle

Explain the mechanics — JSON Pointer segments, ~1 escaping, the fixed set of fields components may hold, and the fact that a definition is inert until referenced.

for a senior

Show you know the failure modes: unresolved pointers, unreferenced components leaking into generated SDKs, and Swagger 2.0 #/definitions/... pointers surviving a botched conversion.

for a principal

Own the naming and ownership rules for the registry — component names become generated type names, so a naming convention and a review gate on new components is a contract-level decision, not a style preference.

## What a $ref actually is OpenAPI documents get large fast, and the same shapes — an error body, a pagination parameter, a `404` response — repeat across dozens of operations. `$ref` is the mechanism that lets you write such a thing once and point at it everywhere else. Wherever the specification allows a **Reference Object**, you may write an object whose single key is `$ref` and whose value is a URI identifying the definition to use instead. ## Reading the pointer A value like `#/components/schemas/Pet` has two parts. Everything before `#` names the document (empty here, meaning *this* document). Everything after `#` is a **JSON Pointer** (RFC 6901): a `/`-separated path of keys walked from the document root. So the resolver takes the root object, looks up `components`, then `schemas`, then the key `Pet`, and uses whatever it finds there. Because `/` separates segments, a key that itself contains a slash must be escaped: `~1` stands for `/` and `~0` stands for `~`. That is why a pointer at a path item looks like `#/paths/~1pets/get` — the path key is literally `/pets`. If the pointer does not resolve, the document is invalid; most tools fail loudly, though a few silently render an empty schema, which is worse. ## The components object `components` is the document's reuse registry. In OpenAPI 3.0 and 3.1 it may hold `schemas`, `responses`, `parameters`, `examples`, `requestBodies`, `headers`, `securitySchemes`, `links` and `callbacks`; 3.1 adds `pathItems`. Each is a map from a name you choose to a definition of that type. Names must match `^[a-zA-Z0-9\.\-_]+$` — no spaces, no slashes — because those names become fragments of pointers and, in practice, class names in generated code. The key property to internalise is that **components is inert**. Defining `components.schemas.Pet` does not create an endpoint, a model exposed to clients, or anything else visible in the API. It becomes part of the contract only when a path, a response, another schema or a parameter references it. A document whose entire `components` section is unreferenced describes an API with no shapes at all. (Some code generators do emit a model class for every entry regardless, which is why stale unused components tend to leak into SDKs.) ## Where $ref may appear A `$ref` is legal only where the specification declares a Reference Object is allowed — a schema position, a response, a parameter list entry, a request body, a header, an example, a link, a callback, and a path item. You cannot, for example, `$ref` half of an operation object or splice a reference into the middle of an arbitrary map. When a tool complains that a reference is "not allowed here", that is usually the reason. Note also that security requirements are the odd one out: an operation's `security` entry names a scheme by its **key** under `components.securitySchemes`, not by `$ref`. ## Version differences worth knowing In OpenAPI 3.0 the Reference Object holds `$ref` and nothing else; any sibling properties SHALL be ignored, so writing a `description` next to a `$ref` to explain a specific usage silently does nothing. OpenAPI 3.1 loosens this in two ways. The Reference Object itself gained optional `summary` and `description`, which override the referenced object's own. And because 3.1 aligns Schema Objects with JSON Schema 2020-12, inside a schema `$ref` is JSON Schema's `$ref`, where sibling keywords are allowed and apply alongside the reference. Swagger 2.0, the predecessor, had no `components` object: reusable schemas lived under a top-level `definitions`, so references read `#/definitions/Pet`. Seeing that pointer in a document claiming `openapi: 3.0.3` is a reliable sign of a bad conversion. ## What tools do with references Validators resolve references to check the document is coherent. Docs renderers usually keep the reference so the rendered page can link to a single named model. Code generators map a referenced schema to one named type and an inline schema to a synthesised name, which is a practical reason to name anything you want to appear as a first-class model in an SDK. Bundlers can rewrite external references into local ones, or inline everything — the latter loses the shared name and duplicates the shape.

  • Does defining a schema under components make it part of the API?
    No. `components` is an inert registry — a definition affects the contract only once a path, response, parameter or another schema references it. An unreferenced entry describes nothing, though some generators still emit a model class for it, which is how dead shapes leak into SDKs.
  • What happens if you write a description next to a $ref in OpenAPI 3.0?
    It is ignored. In 3.0 the Reference Object may contain only `$ref`; sibling properties SHALL be discarded. OpenAPI 3.1 changes this: the Reference Object accepts `summary` and `description` that override the target's, and inside Schema Objects `$ref` follows JSON Schema 2020-12, where sibling keywords do apply.
  • How do you reference something whose key contains a slash, such as the path /pets?
    Escape it in the JSON Pointer: `~1` represents `/` and `~0` represents `~`. So the GET operation on `/pets` is `#/paths/~1pets/get`. Without the escape the resolver treats the slash as a segment separator and the pointer fails to resolve.

saying these in an interview costs you the question

  • Thinks defining a schema under components exposes an endpoint
  • Believes $ref works only for schemas
  • Writes #/definitions/Pet inside an OpenAPI 3.x document
  • Expects a description beside a $ref to render in 3.0
  • Thinks $ref can be spliced in anywhere in the document

context