JSON Schema & OpenAPI: Compatibility, Differences & Migration
OpenAPI 3.1 uses JSON Schema Draft 2020-12 directly — making the two specifications fully compatible for the first time since OpenAPI was created. OpenAPI 3.0 used a restricted subset of Draft-07 with custom extensions such as nullable and discriminator that are incompatible with standard JSON Schema validators. Understanding this split is essential for migrating APIs, sharing schemas across tools, and generating accurate TypeScript types.
The most disruptive incompatibility in OpenAPI 3.0 is nullable: true — an OpenAPI-specific extension that standard validators reject outright. In OpenAPI 3.1, the equivalent is type: ["string", "null"], which is valid JSON Schema 2020-12 understood by every conformant validator.
Need to validate an OpenAPI schema object right now? Jsonic's JSON Schema Validator supports JSON Schema Draft 2020-12 — paste any OpenAPI 3.1 schema component and validate instantly.
Validate OpenAPI schemas in JsonicOpenAPI 3.0 vs 3.1: the 5 key differences at a glance
OpenAPI 3.1 was released in February 2021 and introduced full JSON Schema 2020-12 alignment. The table below summarises the 5 most impactful changes between the 2 versions for teams working with JSON Schema validators, code generators, and documentation tools.
| Feature | OpenAPI 3.0 (Draft-07 subset) | OpenAPI 3.1 (Draft 2020-12) |
|---|---|---|
| Nullable types | nullable: true (proprietary) | type: ["string", "null"] |
External $ref | Internal #/components/schemas only | Any URI, including external .json files |
Sibling keywords with $ref | Silently ignored | Evaluated alongside $ref |
| Standard validator compatibility | No — requires OpenAPI-aware tools | Yes — any Draft 2020-12 validator works |
discriminator | OpenAPI extension only | Maps to oneOf/if/then/else in JSON Schema |
For teams on OpenAPI 3.0, the most urgent migration task is replacing nullable: true fields — they will cause validation failures in any standard JSON Schema tool, including Ajv and the Python jsonschema library.
Migrating nullable fields: OpenAPI 3.0 to 3.1
nullable: true is the single most common OpenAPI 3.0 pattern that breaks standard JSON Schema validators. There are 3 migration patterns depending on the field shape.
Pattern 1: simple scalar type
# OpenAPI 3.0 — NOT valid JSON Schema
name:
type: string
nullable: true
# OpenAPI 3.1 — valid JSON Schema 2020-12
name:
type: [string, "null"]Pattern 2: field with format or other constraints
# OpenAPI 3.0
createdAt:
type: string
format: date-time
nullable: true
# OpenAPI 3.1
createdAt:
type: [string, "null"]
format: date-timePattern 3: nullable $ref (the tricky case)
In OpenAPI 3.0, a $ref with nullable: true is common but particularly non-standard — in 3.0, nullable alongside a $ref is the only way to express a nullable reference. In OpenAPI 3.1, use oneOf:
# OpenAPI 3.0
address:
$ref: '#/components/schemas/Address'
nullable: true # silently ignored by JSON Schema validators
# OpenAPI 3.1 — correct
address:
oneOf:
- $ref: '#/components/schemas/Address'
- type: "null"Tools such as openapi-format and swagger-converter can automate all 3 patterns across an entire specification file in a single pass, covering hundreds of schema objects in seconds.
Using $ref in OpenAPI 3.1 vs 3.0
The $ref keyword is how OpenAPI (and JSON Schema) enables reusable schemas — a single definition used in 10 or 100 places without duplication. OpenAPI 3.1 unlocks 3 major improvements over the restricted 3.0 behaviour.
External file references
In OpenAPI 3.1, $ref can point to any URI — including standalone JSON Schema files on disk or over HTTP. This means a single canonical schema file can be shared between the OpenAPI specification and a standalone validator:
# openapi.yaml — OpenAPI 3.1
components:
schemas:
Address:
$ref: './schemas/address.schema.json' # external JSON Schema file// address.schema.json — standalone JSON Schema 2020-12
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^[0-9]{5}$" }
},
"required": ["street", "city", "zip"]
}Sibling keywords alongside $ref
OpenAPI 3.0 silently ignores any keywords placed alongside a $ref. OpenAPI 3.1 evaluates them as an implicit allOf — the referenced schema and the sibling keywords both apply:
# OpenAPI 3.1 — description alongside $ref is evaluated, not ignored
address:
$ref: '#/components/schemas/Address'
description: "The user's billing address"
nullable: false # (not needed, but evaluated if present)$dynamicRef for recursive schemas
OpenAPI 3.1 also inherits $dynamicRef and $dynamicAnchor from JSON Schema 2020-12, which enables recursive schema definitions (e.g., a tree node that contains child tree nodes) without circular reference workarounds.
For a full reference on $ref syntax and resolution, see the JSON Schema $ref guide.
discriminator in OpenAPI 3.0 vs JSON Schema oneOf
OpenAPI's discriminator object is a documentation and tooling hint that identifies which oneOf/anyOf subschema applies based on a named property value. It has no equivalent keyword in JSON Schema itself — but the same validation behaviour can be expressed with oneOf plus if/then/else.
OpenAPI 3.0/3.1 discriminator pattern
# OpenAPI (both 3.0 and 3.1)
Pet:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'Equivalent JSON Schema 2020-12 (no discriminator)
{
"oneOf": [
{ "$ref": "#/$defs/Cat" },
{ "$ref": "#/$defs/Dog" }
],
"$defs": {
"Cat": {
"type": "object",
"properties": {
"petType": { "const": "cat" },
"indoor": { "type": "boolean" }
},
"required": ["petType", "indoor"]
},
"Dog": {
"type": "object",
"properties": {
"petType": { "const": "dog" },
"breed": { "type": "string" }
},
"required": ["petType", "breed"]
}
}
}The JSON Schema approach relies on const inside each subschema to enforce the discriminating property value. Standard JSON Schema validators use this to determine which branch of oneOf matches without needing the discriminator extension. For conditional required fields, you can also use if/then/else — see the conditional validation guide for examples.
Validating OpenAPI schemas with Ajv
Ajv 8 supports JSON Schema Draft 2020-12 natively, making it the right tool for validating OpenAPI 3.1 schema objects. For OpenAPI 3.0, use @apidevtools/swagger-parser instead — it understands nullable and other 3.0 extensions that Ajv will reject.
Validating an OpenAPI 3.1 schema component with Ajv
import Ajv2020 from 'ajv/dist/2020'
const ajv = new Ajv2020()
// An OpenAPI 3.1 schema object — valid JSON Schema 2020-12
const userSchema = {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: ['string', 'null'], format: 'email' } // nullable in 3.1
},
required: ['id', 'name']
}
const validate = ajv.compile(userSchema)
console.log(validate({ id: 1, name: 'Alice', email: null })) // true
console.log(validate({ id: 1, name: 'Alice', email: 'a@b.com' })) // true
console.log(validate({ name: 'Alice' })) // false — id is required
console.log(validate.errors)
// [{ instancePath: '', keyword: 'required', params: { missingProperty: 'id' } }]Validating a full OpenAPI 3.1 document with swagger-parser
import SwaggerParser from '@apidevtools/swagger-parser'
async function validateSpec(specPath: string) {
try {
const api = await SwaggerParser.validate(specPath)
console.log('Valid OpenAPI', api.info.version, '—', api.info.title)
} catch (err) {
console.error('Invalid spec:', err.message)
}
}
validateSpec('./openapi.yaml')swagger-parser handles both OpenAPI 3.0 and 3.1, resolves all $refs (including external files), and validates the overall OpenAPI document structure — not just individual schema objects. Use Ajv for per-schema validation at runtime; use swagger-parser for full-spec validation at build time.
For a deep dive on using Ajv with JSON Schema, see the Ajv JSON Schema guide.
Generating TypeScript types from OpenAPI schemas
TypeScript type generation from OpenAPI schemas is one of the biggest productivity wins for API-first teams. 3 tools cover the most common workflows in 2026.
openapi-typescript (recommended)
openapi-typescript generates TypeScript types from OpenAPI 3.0 and 3.1 documents. It is the most widely adopted generator with over 5 million weekly npm downloads and full support for all 2020-12 features:
# Install
npm install -D openapi-typescript
# Generate types from a local spec
npx openapi-typescript openapi.yaml -o src/types/api.d.ts
# Generate types from a remote spec
npx openapi-typescript https://api.example.com/openapi.json -o src/types/api.d.tsThe generated output looks like this for a simple schema:
// src/types/api.d.ts (generated)
export interface components {
schemas: {
User: {
id: number
name: string
email: string | null // type: ["string", "null"] → string | null
}
Pet: components['schemas']['Cat'] | components['schemas']['Dog']
Cat: { petType: 'cat'; indoor: boolean }
Dog: { petType: 'dog'; breed: string }
}
}openapi-fetch: type-safe API calls
Pair openapi-typescript with openapi-fetch for end-to-end type safety from schema to HTTP call — request bodies, response types, and path parameters are all inferred from the generated types:
import createClient from 'openapi-fetch'
import type { paths } from './types/api'
const client = createClient<paths>({ baseUrl: 'https://api.example.com' })
// TypeScript knows the request body shape and response type
const { data, error } = await client.GET('/users/{id}', {
params: { path: { id: 42 } }
})Comparison of OpenAPI type generation tools
| Tool | OpenAPI 3.0 | OpenAPI 3.1 | Runtime validation | Weekly downloads |
|---|---|---|---|---|
openapi-typescript | Yes | Yes | No (types only) | ~5M |
@openapitools/openapi-generator-cli | Yes | Partial | Yes (full client) | ~500k |
@openapi-to-zod | Yes | Yes | Yes (Zod schemas) | ~50k |
OpenAPI extensions not valid in JSON Schema
OpenAPI 3.0 introduced 8 proprietary extensions to JSON Schema Draft-07. Understanding which keywords are OpenAPI-only is critical when reusing schemas with standard validators.
| Keyword | OpenAPI 3.0 meaning | Standard JSON Schema equivalent | Valid in 3.1? |
|---|---|---|---|
nullable | Allows null alongside the declared type | type: ["T", "null"] | No — deprecated |
discriminator | Hints which oneOf branch applies | oneOf with const per branch | Yes — retained as OpenAPI extension |
readOnly | Field is read-only in responses | readOnly (JSON Schema 2019-09+) | Yes — now a standard keyword |
writeOnly | Field is write-only in requests | writeOnly (JSON Schema 2019-09+) | Yes — now a standard keyword |
xml | XML serialisation metadata | None | Yes — retained as OpenAPI extension |
externalDocs | Link to external documentation | None (use $comment) | Yes — retained as OpenAPI extension |
example | Single example value | examples array (JSON Schema 2019-09+) | Yes — both are valid |
x-* | Vendor/custom extensions | None — always ignored by validators | Yes — always permitted |
The 3 keywords that cause the most validation failures when moving from OpenAPI 3.0 to standard JSON Schema are nullable, discriminator, and example (singular). Swap them for their JSON Schema 2020-12 equivalents and your schema will validate cleanly with any conformant tool.
Frequently asked questions
What is the difference between OpenAPI 3.0 and 3.1 for JSON Schema?
OpenAPI 3.0 uses a restricted subset of JSON Schema Draft-07 with custom extensions —nullable, discriminator, and others — that are not valid standard JSON Schema. OpenAPI 3.1, released in February 2021, adopts JSON Schema Draft 2020-12 in full across all 3 areas: full keyword support, full $ref resolution semantics, and no proprietary deviations. Any standard Draft 2020-12 validator can validate an OpenAPI 3.1 schema object without modification. Practically, the most impactful change is that nullable: true (3.0) is replaced by type: ["string", "null"] (3.1 / JSON Schema 2020-12), and sibling keywords alongside $ref are no longer ignored.
How do I convert OpenAPI 3.0 nullable fields to OpenAPI 3.1?
Replace nullable: true alongside the type with a JSON Schema 2020-12 type array. For example, { type: string, nullable: true } becomes { type: [string, null] }. If the field uses a $ref, wrap it in a oneOf:
# Before (OpenAPI 3.0)
address:
$ref: '#/components/schemas/Address'
nullable: true
# After (OpenAPI 3.1)
address:
oneOf:
- $ref: '#/components/schemas/Address'
- type: "null"Automated migration tools such as openapi-format and swagger-converter can handle bulk transformation across large specifications covering hundreds of schema objects in a single pass.
Can I use standard JSON Schema validators with OpenAPI schemas?
With OpenAPI 3.1, yes — schema objects are valid JSON Schema Draft 2020-12 and work with any conformant validator. Ajv 8 requires the draft-2020-12 option:
import Ajv2020 from 'ajv/dist/2020'
const ajv = new Ajv2020()
const validate = ajv.compile(myOpenApi31SchemaObject)With OpenAPI 3.0, standard validators will reject nullable: true and other extensions. For 3.0 schemas, use @apidevtools/swagger-parser — it is OpenAPI-aware and handles all 3.0 extensions correctly. See the Ajv guide for full setup instructions.
How does $ref work in OpenAPI 3.1 compared to 3.0?
In OpenAPI 3.0, $ref could only point to objects inside the same document under #/components/schemas, and sibling keywords alongside a $ref were silently ignored. In OpenAPI 3.1, $ref follows full JSON Schema 2020-12 semantics: it can reference external JSON Schema files directly (e.g., "$ref": "./address.schema.json"), sibling keywords are evaluated alongside the $ref, and $dynamicRef/$dynamicAnchor are available for recursive schemas. This makes it straightforward to share 1 schema file between an OpenAPI spec and standalone JSON Schema validators without duplication.
How do I generate TypeScript types from an OpenAPI JSON schema?
The most popular tool is openapi-typescript with over 5 million weekly downloads. Run it against any local or remote spec:
npx openapi-typescript openapi.yaml -o src/types/api.d.tsIt supports both OpenAPI 3.0 and 3.1, correctly maps type: ["string", "null"] to string | null, and mapsnullable: true in 3.0 specs to T | null in the emitted TypeScript. For runtime validation alongside types, pair it with openapi-fetch (type-safe fetch client) or use @openapi-to-zod to generate Zod schemas.
What OpenAPI extensions are not valid JSON Schema?
In OpenAPI 3.0, the following proprietary keywords are not recognised by standard JSON Schema validators: nullable (use type array in 3.1), discriminator (maps to oneOf with const per branch), xml (serialisation hint), externalDocs (documentation link), and example (singular — use the examples array from JSON Schema 2019-09+). Any keyword starting with x- is a vendor extension and is always ignored by standard validators. In OpenAPI 3.1, readOnly and writeOnly are now standard JSON Schema keywords and are no longer OpenAPI-specific.
Key definitions
- OpenAPI Specification
- A language-agnostic interface description for HTTP APIs, maintained by the OpenAPI Initiative. Version 3.1 (released February 2021) aligns fully with JSON Schema Draft 2020-12.
- nullable
- An OpenAPI 3.0-specific boolean extension that allows a field to also have the value
null. Not a valid JSON Schema keyword — standard validators reject it. Replaced by type arrays (type: ["T", "null"]) in OpenAPI 3.1. - discriminator
- An OpenAPI object that identifies which subschema in a
oneOf/anyOfapplies, based on the value of a named property. Not a JSON Schema keyword — it is an OpenAPI tooling hint with no validation semantics in standard validators. - $ref
- A JSON Schema keyword that references another schema by URI. In OpenAPI 3.1,
$refsupports any URI including external files, and sibling keywords are evaluated alongside the referenced schema. See the $ref guide for full syntax. - Schema Object
- The OpenAPI term for an individual schema definition — equivalent to a JSON Schema document. In OpenAPI 3.1, schema objects are JSON Schema 2020-12 documents with no modifications.
- swagger-parser
- The
@apidevtools/swagger-parsernpm package. Validates and dereferences OpenAPI 2.0, 3.0, and 3.1 documents — resolves all$refs and checks the full OpenAPI document structure, including extensions not covered by standard JSON Schema validators.
Validate your OpenAPI schema objects
Paste any OpenAPI 3.1 schema component into Jsonic's JSON Schema Validator to check for invalid extensions, missing required fields, and type errors — all with JSON Schema Draft 2020-12 support.
Validate OpenAPI schemas in Jsonic