flashman
← All guides

JSON Schema references and sibling keywords

Apply JSON Schema $ref across drafts, sibling keywords, bundlers, base URIs, cycles, and anchors so every validator executes the contract you published.

2026-09-29 · 8 min read

  • json-schema
  • validation
  • references

JSON Schema references let one contract reuse another, but reference behavior changed when dialects stopped treating $ref as a complete replacement for its sibling keywords. A schema that only carried annotations beside $ref in an older draft can start enforcing those siblings after an upgrade.

References also depend on identity. Base URIs, anchors, bundlers, and remote registries decide which document is evaluated. A readable $ref string is not, by itself, proof of the schema that production will apply.

Pin the dialect before designing reuse

Declare the $schema URI and verify that editors, gateways, and runtime validators implement that dialect. In drafts that ignore siblings of $ref, put real constraints inside the target schema or an applicator such as allOf. In drafts that apply siblings, document which adjacent keywords are intentional validation.

  • State the draft in the published schema.
  • Avoid relying on ignored keywords for enforcement.
  • Keep annotations and assertions visually distinct.
  • Re-test when a validator or bundler is upgraded.

Give every document a stable identity

Use absolute $id values for documents that other schemas reference, and keep relative references resolvable from that base. Anchors should name reusable subschemas without depending on a file path that a bundler will rewrite.

When a registry serves schemas by URL, treat the retrieved bytes as versioned content. Changing a schema at a previously published URI changes every consumer that still points at it.

Bundle without changing evaluation

A bundler may inline references, rewrite pointers, or leave remote references in place. Compare validation results before and after bundling on the same fixtures. Cyclic references need a representation that the runtime understands rather than an inliner that expands forever.

If the source is split across files for review, store the bundled artifact that production actually loads and review the diff when either side changes.

Keep sibling behavior explicit

If a sibling keyword must apply in every dialect you support, do not leave it only beside $ref. Wrap the reference and the extra constraint in a form that all of those dialects evaluate. If a sibling must not apply, remove it instead of depending on an old draft to ignore it.

Patterns, enums, and unevaluated keywords beside a reference are the usual surprises. Read the specification section for the selected draft rather than copying a snippet from a different dialect.

Test resolution and enforcement separately

Use Flashman's JSON formatter for source and bundled schemas, diff for validator results, the YAML/JSON converter for configuration copies, the regex tester for pattern keywords, and the Markdown editor for the dialect matrix.

Cover missing targets, cyclic references, sibling keywords, changed base URIs, anchors, remote cache hits, bundled and multi-file forms, dialect upgrades, and every validator that accepts the schema in production.

Try these tools