Structured output / JSON mode

Force the model to emit valid, schema-conforming JSON every time. No regex scraping, no “pretty-please return JSON” prompts, no silent parse failures in production.

Quick start

import OpenAI from "openai";

const openai = new OpenAI();

const schema = {
  type: "json_schema",
  json_schema: {
    name: "sentiment",
    strict: true,
    schema: {
      type: "object",
      properties: {
        sentiment: {
          type: "string",
          enum: ["positive", "negative", "neutral"],
        },
        confidence: { type: "number" },
        keywords: {
          type: "array",
          items: { type: "string" },
        },
      },
      required: ["sentiment", "confidence", "keywords"],
      additionalProperties: false,
    },
  },
};

const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [
    {
      role: "system",
      content: "Classify sentiment. Return JSON matching the schema.",
    },
    { role: "user", content: "I absolutely love this product!" },
  ],
  response_format: schema,
});

const result = JSON.parse(
  completion.choices[0].message.content ?? "{}"
);
// result.sentiment === "positive"
// result.confidence === 0.97
// result.keywords === ["love", "product"]

How it works

Set response_format to { type: "json_schema", json_schema: { ... } } and the model’s output is constrained to valid JSON that matches your schema. No more, no less.

Under the hood, the API applies constrained sampling — tokens that would violate the schema are masked from the probability distribution. The model literally cannot emit invalid JSON or extra keys.

Strict mode

Set strict: true inside json_schema to guarantee:

  • All required fields are present.
  • No additionalProperties leak through.
  • enum values are respected exactly.
  • Types match — no string where a number is expected.

Without strict mode, the model may occasionally add extra keys or omit optional-but-expected fields. Always enable it for production pipelines.

Legacy: json_object mode

Before json_schema shipped, the only option was response_format: { type: "json_object" }. It guarantees valid JSON but does not enforce a schema. You still need to prompt the model with the shape you want and validate the result yourself.

const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [
    {
      role: "system",
      content:
        "Return JSON: { \"sentiment\": \"positive\"|\"negative\"|\"neutral\", \"confidence\": number }",
    },
    { role: "user", content: "I hate waiting on hold." },
  ],
  response_format: { type: "json_object" },
});

Prefer json_schema for new work. It eliminates the prompt-engineering fragility and gives you compile-time–style guarantees at runtime.

Parsing the response

The model returns a standard chat completion. The JSON lives in choices[0].message.content. Always wrap JSON.parse in a try/catch — even with schema enforcement, network errors or truncated streams can yield unparseable strings.

function safeParse<T>(raw: string | null): T | null {
  if (!raw) return null;
  try {
    return JSON.parse(raw) as T;
  } catch {
    console.error("Failed to parse structured output:", raw.slice(0, 200));
    return null;
  }
}

const data = safeParse<SentimentResult>(
  completion.choices[0].message.content
);
if (!data) {
  // fallback: retry, use default, or escalate
}

Supported models

json_schema works with gpt-4o, gpt-4o-mini, and the o-series models. Older models (gpt-4, gpt-3.5-turbo) only support the legacy json_object mode.