Room 42, home

Structured outputs: get reliable JSON from Claude

How to get reliable, structured results like JSON out of Claude in practice, and how to check you got exactly the shape you asked for.

Codingintermediate6 min readUpdated 12 Jun 2026

In short

  1. 01Getting reliable machine-readable output from Claude needs a structural constraint, not just a firmer please-return-JSON.
  2. 02Three approaches get stronger in turn: a tight prose instruction, writing to a templated file, and describing the full schema.
  3. 03Describing the schema in the prompt is the safest default, since Claude treats it as a contract and self-validates before responding.
  4. 04Always add a validation step on your side and design clear schemas with specific types, enums, and only genuinely required fields.
Contents · 10 sections
  1. 1Why is "return JSON" alone not enough?
  2. 2What are the ways to get structured output from Claude?
  3. 3Approach 1: Tight prose instruction
  4. 4Approach 2: Write to a file with a template
  5. 5Approach 3: Describe the schema in the prompt
  6. 6How do I validate Claude's output?
  7. 7The session flow
  8. 8How do I design a good schema?
  9. 9Which approach should I use?
  10. 10What to read next

Getting Claude to return prose is easy. Getting it to return machine-readableOutput a program can parse directly, like a JSON object, rather than free-flowing prose a human reads. output your scripts can depend on is a different problem. This guide covers the full spectrum in the Claude Code CLI: why naive prompting fails, three progressively stronger techniques, and how to verify the result on your side.

Why is "return JSON" alone not enough?

A prompt that says "always respond with valid JSON" works until it doesn't. Claude may:

  • Wrap the object in a markdown fence
  • Prepend a sentence ("Sure, here is the JSON:")
  • Omit a required field when the answer is ambiguous
  • Return valid JSON whose schema differs from what you expected

These failures are non-deterministicThe same prompt can give a slightly different answer each time, so a one-off test passing does not guarantee the next run behaves the same.. They are rare enough to pass a quick check and frequent enough to break a downstream script in production. The fix is not a better adjective in the prompt. The fix is a structural constraint, communicated clearly.

What are the ways to get structured output from Claude?

ApproachSchema enforcedBest for
Tight prose instructionNoQuick one-off extractions, human-reviewed output
Write-to-file with a templateSoft (shape defined by template)Files you will parse in a next step
Describe the schema in the promptYes (Claude self-validates)Repeatable pipelines, scripts consuming the output

Approach 1: Tight prose instruction

The simplest fix is being explicit about every constraint in the prompt. Vague instructions invite creative interpretation. Precise instructions do not.

Try this prompt
Extract the following fields from the text below and respond with ONLY a JSON object, no prose, no markdown fences, no explanation. Fields: name (string), age (integer), city (string). If a field is missing, use null. Text: "My name is Ada Lovelace, I am 36 years old, city: London."

Run this in the terminal:

claude "Extract the following fields from the text below and respond with ONLY a JSON object, no prose, no markdown fences, no explanation. Fields: name (string), age (integer), city (string). If a field is missing, use null. Text: 'My name is Ada Lovelace, I am 36 years old, city: London.'"

What you get: Output that is usually a clean JSON object.

What you lose: No guarantee on field presence or type. If the text is ambiguous Claude may still add a note. You must validate before using the output downstream.

When to use it: One-off extractions you are reviewing yourself, or as a first pass before tightening the approach.

Approach 2: Write to a file with a template

For tasks you run repeatedly, telling Claude to write output to a specific file in a known shape is more reliable than capturing stdout. Claude treats the file as the deliverable and is less likely to decorate it with prose.

Create a template file that shows the exact shape you want:

{
  "name": null,
  "age": null,
  "city": null
}

Then prompt Claude to fill it:

Try this prompt
Fill in the JSON file at output/person.json. Replace each null with the correct value extracted from the text. Do not add fields, do not remove fields, do not change the key names. Text: "My name is Ada Lovelace, I am 36 years old, city: London."
claude "Fill in the JSON file at output/person.json. Replace each null with the correct value extracted from the text. Do not add fields, do not remove fields, do not change the key names. Text: 'My name is Ada Lovelace, I am 36 years old, city: London.'"

After Claude finishes, validate the file with any JSON parser:

node -e "const d = require('./output/person.json'); console.log(d)"
# or
python3 -c "import json; print(json.load(open('output/person.json')))"

What you get: A file in a known location with the expected shape. Easier to review in version control, easier to pass to a next step.

What you lose: Claude can still change a value's type. The template enforces shape, not types.

When to use it: Batch extraction tasks, data-prep pipelines, any flow where a next script reads the file.

Approach 3: Describe the schema in the prompt

The most reliable technique for repeatable work is giving Claude the full schemaA description of the exact shape your output must take: which fields exist, their types, and which are required. Acts as a contract the model fills in. as part of the prompt: field names, types, whether each field is required, and what valid values look like. Claude reads this as a contract and self-validates before responding. This is the same shape as the input schema on a tool definition, where a typed contract tells the model exactly what to return.

Try this prompt
Respond with a single JSON object matching this schema exactly. No prose, no markdown fences. Schema: - name: string, required - age: integer (whole number, not a float), required - city: string, required - status: string, one of "active" | "inactive" | "pending", required If a field cannot be determined from the input, respond with an error object: {"error": "missing field: <fieldname>"}. Input: "Ada Lovelace, 36, London, currently active."
claude --output-format json "Respond with a single JSON object matching this schema exactly. No prose, no markdown fences.
 
Schema:
- name: string, required
- age: integer, required
- city: string, required
- status: string, one of active | inactive | pending, required
 
If a field cannot be determined, respond: {'{"error": "missing field: fieldname"}'}.
 
Input: 'Ada Lovelace, 36, London, currently active.'"

The --output-format json flag tells the CLI to return only the model's text in a JSON envelope, which makes piping to jq straightforward:Common gotchaDo not read --output-format json as a guarantee that the model's answer is valid JSON.The flag only wraps the CLI's own metadata (cost, session id, the raw text) in an envelope, so .result can still be a markdown-fenced string or carry a stray sentence. Always parse and validate .result itself before trusting it.

claude --output-format json "..." | jq '.result'

What you get: Output that conforms to the schema in the vast majority of cases. Claude also self-reports when a field is missing rather than hallucinating a value.

What you lose: You are still relying on Claude following the contract. Run a validation step on your side.

When to use it: Any pipeline where downstream code will parse the output. This is the safest default.

How do I validate Claude's output?

Regardless of which approach you use, add a validation step before the output enters production. Claude follows the schema reliably, but your business logic may have constraints a prose schema cannot fully express: value ranges, cross-field rules, date formats.

A one-liner validation in bash (checks that the output is valid JSON at minimum):

cat output/person.json | python3 -m json.tool > /dev/null && echo "valid JSON" || echo "parse error"

For stricter checks, write a small validation script and ask Claude to run it as part of the task:

Try this prompt
After writing output/person.json, run scripts/validate-person.js. If it exits non-zero, fix the JSON and run validation again. Do not stop until validation passes.

This turns Claude Code into a self-correcting loop: write, validate, fix, repeat. You get the output and a passing validation in one session, with no manual intervention.

The session flow

How do I design a good schema?

Good schemas reduce failures regardless of the approach you pick.

Be specific with types. "integer (whole number, not a float)" is better than "number" when you mean a whole number. Claude reads the description, not a type annotation.Common gotchaA prose schema describes types, it does not enforce them, so a field can come back as "36" (a string) or 36.0 when you wanted 36.Coerce and check types in your validation step rather than assuming the JSON parsed means the types are right.

Use enumsA fixed list of allowed values for a field, so the model must pick one of them instead of inventing its own wording. for categorical fields. List the allowed values explicitly: one of "active" | "inactive" | "pending". This eliminates creative values like "enabled" or "on".

Mark only required fields as required. Over-constraining causes Claude to hallucinate values for fields that are not present in the input. If a field can be absent, say so and tell Claude what to put when it is.

Describe each field. "ISO 8601 date string, e.g. 2026-06-12" is more reliable than "string". The description is part of Claude's context; use it.

Flat schemas first. Flat schemas are easier to validate and easier to debug in a CLI session. Add nesting only when the domain structure demands it.

Which approach should I use?

Do you need to parse the output in a script?
├── No  → Tight prose instruction is enough.
│          Review the output manually before using it.
└── Yes → Will you run this more than once?
           ├── No  → Write-to-file with a template.
           │          Shape is enforced; verify with a JSON parser.
           └── Yes → Describe the full schema in the prompt.
                      Add a validation script Claude can run and retry.

In practice, describing the schema in the prompt plus a validation step covers almost every production use case. The write-to-file approach is useful when you want a diff-friendly artefact in version control. Tight prose is fine for one-offs you are reviewing yourself.

Long extraction tasks benefit from giving Claude a plan before it starts writing. Use Shift+Tab to enter plan mode, review the steps, then approve. The agent will follow the plan and self-correct deviations, which is especially useful when the output shape is complex.