JSON Schema Draft Comparison: 04 vs 07 vs 2019-09 vs 2020-12

If you have worked with JSON Schema for more than a year, you have probably encountered the dreaded "draft incompatibility" problem. A schema written for Draft 04 fails to validate in a Draft 07 validator, and you have no idea why.

Here is what changed between each draft and how to handle the transition.

Draft 04 (2013)

The first widely adopted JSON Schema draft. Still in active use in many tools and specifications.

Key syntax:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "http://example.com/schemas/user.json",
  "type": "object",
  "properties": {
    "name": { "type": "string" }
  },
  "required": ["name"]
}

Limitations:

  • id is a keyword (conflicts with the HTML attribute id)
  • No if/then/else support
  • exclusiveMinimum and exclusiveMaximum are booleans, not numbers
  • No const keyword

Draft 07 (2018)

The most significant update. Added the features developers were requesting most.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/schemas/user.json",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": {
      "type": "integer",
      "exclusiveMinimum": 0,  // Now accepts a number instead of boolean!
      "exclusiveMaximum": 150
    },
    "role": {
      "type": "string",
      "const": "admin"  // New in draft 07!
    },
    "discount": {
      "type": "number",
      "if": { "exclusiveMaximum": 100 },
      "then": { "multipleOf": 5 },
      "else": { "multipleOf": 10 }
    }
  },
  "required": ["name"]
}

New features:

  • $id replaces id (backward-incompatible change!)
  • if/then/else conditional validation
  • const keyword (a single allowed value)
  • exclusiveMinimum/exclusiveMaximum now accept numbers
  • contentEncoding and contentMediaType
  • propertyNames validation

The id$id breaking change is the most common migration issue. Schema that used "id": "http://..." in Draft 04 must use "$id": "http://..." in Draft 07.

On Stack Overflow, the question about draft differences has 30,000 views. The most common migration mistake is keeping the old id keyword in a Draft 07 schema.

Draft 2019-09

Added vocabularies as a formal extension mechanism:

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "https://example.com/schema",
  "$vocabulary": {
    "https://json-schema.org/draft/2019-09/vocab/core": true,
    "https://json-schema.org/draft/2019-09/vocab/applicator": true,
    "https://json-schema.org/draft/2019-09/vocab/validation": true
  },
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "format": "email",
      "deprecated": true,  // New: marks a property as deprecated
      "comment": "Use 'contact_email' instead"  // New: schema annotations
    }
  },
  "unevaluatedProperties": false  // New: catches additional properties
}

Key additions:

  • $vocabulary for declaring which vocabularies are used
  • deprecated annotation
  • comment annotation
  • unevaluatedProperties and unevaluatedItems
  • $recursiveRef and $recursiveAnchor for recursive schemas
  • $defs replaces definitions (old keyword still supported)

Draft 2020-12 (Latest)

The current standard:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schema",
  "$defs": {  // $defs replaces definitions (official)
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" }
      }
    }
  },
  "type": "object",
  "properties": {
    "shipping": { "$ref": "#/$defs/address" },
    "billing": { "$ref": "#/$defs/address" }
  },
  "prefixItems": [  // New: replaces tuple validation in items
    { "type": "string" },
    { "type": "integer" },
    { "type": "string" }
  ],
  "items": { "type": "string" }  // Now only for uniform arrays
}

Key changes from 2019-09:

  • prefixItems replaces items for tuple validation
  • $defs is the official replacement for definitions (was unofficially supported in 2019-09)
  • $ref no longer applies sibling keywords (breaking change!)
  • New format system: format validation is opt-in by default

Migration Decision

DraftWhen to Use
Draft 04Legacy systems, tools that only support 04
Draft 07Most compatibility, widest tool support
2019-09When you need vocabularies or unevaluatedProperties
2020-12New projects with modern validators

My recommendation: Use Draft 07 for maximum compatibility (every major validator supports it). Use 2020-12 for new projects if your validation library supports it.

Common Migration Issues

// Draft 04 →
{
  "id": "http://example.com/schema",
  "exclusiveMinimum": true,
  "minimum": 0
}

// Draft 07 → (breaking changes)
{
  "$id": "http://example.com/schema",  // id → $id
  "exclusiveMinimum": 0                // boolean → number
}
// Draft 2019-09 →
{
  "definitions": { "user": { ... } }
}

// Draft 2020-12 →
{
  "$defs": { "user": { ... } }  // $defs replaces definitions
}

Related Searches

  • json schema draft 04 vs 07
  • json schema draft 2020-12 changes
  • json schema migration draft 04 to 07
  • json schema id vs $id
  • json schema breaking changes
  • json schema draft comparison
  • json schema which draft to use
  • json schema versions history

Frequently Asked Questions

Which JSON Schema draft should I use for a new project?

Use Draft 07 for broadest tool compatibility. Use Draft 2020-12 if your validator supports it and you need the latest features (prefixItems, $defs, unevaluatedProperties). Draft 04 should only be used for legacy compatibility.

What is the most breaking change between drafts?

The id$id rename between Draft 04 and Draft 07. In Draft 04, the schema identifier was "id". In Draft 07 and later, it is "$id". Using the wrong keyword means the schema ID is not recognized, which breaks $ref resolution.

Can I use a Draft 07 schema with a Draft 2020-12 validator?

Yes, if you set the $schema keyword correctly. Validators use $schema to determine which draft rules to apply. A Draft 07 schema with $schema: "http://json-schema.org/draft-07/schema#" will work in a Draft 2020-12 validator.

What happened to definitions in newer drafts?

definitions was replaced by $defs starting in Draft 2019-09. The old definitions keyword is still supported for backward compatibility but is deprecated. New schemas should use $defs.

Final Thoughts

JSON Schema draft compatibility is a manageable problem if you (1) always include the $schema keyword, (2) use Draft 07 unless you need a specific newer feature, and (3) test your schemas with your specific validator version.