The version bump that fixed JSON Schema
For years OpenAPI had an awkward secret: its "JSON Schema" wasn't really JSON Schema. OpenAPI 3.0 was based on an old JSON Schema draft (Draft 05) with its own dialect of tweaks, so schemas you wrote for validation didn't drop cleanly into an OpenAPI file, and vice versa. OpenAPI 3.1 fixes this at the root: it is fully aligned with [JSON Schema 2020-12](/blog/json-schema-2020-12-guide). By early 2026 the major gateways, docs platforms, and codegen tools default to 3.1, and 3.0-only stacks are starting to hit compatibility walls.
If you maintain an API contract — or generate one from code — this is the migration worth understanding. Here's exactly what changed and how to move.
Change 1: `nullable` is gone — use a type array
This is the change that breaks the most specs. In 3.0 you marked a field nullable with a dedicated keyword:
{ "type": "string", "nullable": true }JSON Schema 2020-12 has no nullable keyword — instead type can be an *array*. So 3.1 expresses the same thing the standard way:
{ "type": ["string", "null"] }Any tool that reads 3.1 ignores nullable, so leaving it in silently drops your null handling. This is the single most important find-and-fix of the migration.
Change 2: `example` becomes `examples`
3.0 used a singular example inside schema objects. 2020-12 uses a plural examples array:
{ "type": "string", "format": "email", "examples": ["ada@example.com"] }(The example keyword still exists on media types and parameters — only the *schema-level* keyword changed to the array form.)
Change 3: `$ref` can stand on its own
In 3.0, a $ref had to appear alone; sibling keywords next to it were ignored. 2020-12 lets $ref coexist with other keywords, so you can reference a shared schema *and* add a description or override in the same object:
{
"$ref": "#/components/schemas/Address",
"description": "Billing address; overrides the shared description."
}This makes component reuse far less painful and removes a whole class of "why is my description missing?" surprises.
Change 4: real webhooks
Before 3.1, an OpenAPI file could only describe requests a client *sends to you*. It had no way to describe calls *your API sends out* — the webhooks you POST to a subscriber. 3.1 adds a top-level webhooks object that describes those outbound event payloads with the same schema machinery as regular operations:
{
"webhooks": {
"orderShipped": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/OrderShippedEvent" }
}
}
}
}
}
}
}This pairs directly with webhook payload design — now the contract lives in the same file as the rest of your API.
Change 5: modern schema keywords unlocked
Because 3.1 speaks full 2020-12, you get expressive keywords 3.0 lacked:
- `if` / `then` / `else` for conditional validation, instead of contorted nested
allOf/oneOf. - Tuple validation via
prefixItemsfor fixed-position arrays. - `const`,
$defs,unevaluatedProperties, and the rest of the 2020-12 vocabulary.
A practical migration checklist
- Bump the version string to
"openapi": "3.1.0". - Replace every `nullable: true` with a
["type", "null"]type array. This is the one that changes behavior — do it first and grep for stragglers. - Rename schema-level `example` to `examples` and wrap the value in an array.
- Collapse `$ref` workarounds — you can now put siblings next to
$ref, so unwind anyallOf-with-single-ref hacks you added just to attach a description. - Move outbound events into the new
webhooksobject if you document them. - Re-run codegen and linting against 3.1-aware tooling; older generators may not understand type arrays yet.
- Validate the result. Generate a schema for sample payloads and validate them against your components so the migration didn't quietly loosen a constraint. Format the spec to review large diffs.
Should you migrate now?
Yes, if your tooling supports it — and in 2026 most does. The payoff is that your API schemas and your standalone JSON Schema validators finally speak the same language, so a type defined once can validate requests, generate clients, and drive documentation without translation. The main risk is a stale generator or validator that hasn't caught up to 2020-12; check your toolchain before committing. For the schema fundamentals see the JSON Schema 2020-12 guide and OpenAPI and JSON Schema.