# Structured outputs: get reliable JSON from Claude

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](/guides/write-a-prompt-claude-can-follow) do not.

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:

```bash
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:

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

Then prompt Claude to fill it:

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."

```bash
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:

```bash
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](/guides/tool-calling-explained), where a typed contract tells the model exactly what to return.

```text
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: "}.

Input: "Ada Lovelace, 36, London, currently active."
```

```bash
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:Do 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.

```bash
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):

```bash
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:

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](/guides/claude-code-workflows): 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.A 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](/guides/claude-code-effort-levels), 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.
