Guide

A schema is part of the prompt. Field names and description strings are read by the model and steer its output. A well-designed schema makes the right answer easy to produce. A poorly designed one makes wrong answers easy.

Concept

Before you start: a nullable field is one that may hold a value or null. A required field must appear in every object. An enum is a fixed list of allowed values.

The big idea

Design a schema the way you would design an API. Keep it shallow. Make closed sets into enums. Say which fields are required. Use descriptions to answer the questions a careful human would ask.

The analogy is a paper form. A form with clear boxes gets filled in correctly. A form with one big box labeled "notes" gets a paragraph back. Schemas with free-text fields get paragraphs back too.

Walking through it

Here is a simplified invoice schema, with each design choice marked:

JSON
{
  "type": "object",
  "additionalProperties": false,
  "required": ["vendor", "invoice_id", "currency", "total_cents", "status"],
  "properties": {
    "status": {
      "type": "string",
      "enum": ["ok", "incomplete"],
      "description": "incomplete if any required commercial field is missing from the email"
    },
    "vendor": {"type": "string"},
    "invoice_id": {
      "type": ["string", "null"],
      "description": "Exact invoice id from the text, e.g. INV-4412. Null if not present. Never invent."
    },
    "currency": {"type": "string", "enum": ["USD", "EUR", "GBP", "UNKNOWN"]},
    "total_cents": {"type": ["integer", "null"]}
  }
}

Each choice does something:

  • Enums for status and currency close the set. The model cannot write "dollars."
  • Nullable invoice_id gives an honest way to say "not present."
  • Descriptions act as micro-prompts. "Never invent" sits next to the field it applies to, where the model reads it.
  • Integer cents for total_cents keep money exact. A float such as 1204.5 invites rounding bugs.

Rules of thumb

  • Prefer flat shapes. Deep nesting raises error rates and slows constrained decoding.
  • Use one convention for "unknown." Choose null or an omitted key. Do not mix missing keys, empty strings and "N/A".
  • Put the reasoning field before the answer field when the provider keeps key order. A short rationale before the enum reduces flip-flopping. Keep it short, because it costs output tokens.
  • Cap list lengths with maxItems, so the output cannot grow without limit.

Common mistakes

  • Large free-text fields inside a structured object. They bring back the prose problems the schema was meant to remove.
  • Over-precise number formats. multipleOf: 0.01 fights how decimals are tokenized. Validate money in code, using integer cents.
  • Assuming all JSON Schema features work everywhere. Arbitrary oneOf trees and some additionalProperties settings are often unsupported. Check your provider's subset.

Check yourself

  • Why is "enum": ["ok", "incomplete"] better than a free string? The model can only pick one of the two values, so the output can be checked without a parser for prose.
  • Why store total_cents as an integer? Integer cents are exact. A float can turn $1,204.50 into a rounding error.

Key takeaways

  • A schema is a contract and a prompt. Field names and descriptions both steer the output.
  • Use enums for closed sets, nullable fields for unknowns and integer cents for money.
  • Keep one convention for unknown values.
  • Keep the shape shallow, and cap list sizes.

Further learning

Next, see what to do when an object passes the schema but fails a business rule, in validation and repair.