Getting Claude to return prose is easy. Getting it to return machine-readable 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-deterministic. 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?
| Approach | Schema enforced | Best for |
|---|---|---|
| Tight prose instruction | No | Quick one-off extractions, human-reviewed output |
| Write-to-file with a template | Soft (shape defined by template) | Files you will parse in a next step |
| Describe the schema in the prompt | Yes (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.
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:
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 schema 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.
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:
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 enums 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.
What to read next
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.