For developers building applications powered by Large Language Models (LLMs), one of the most persistent challenges has been reliably extracting structured data. For years, we relied on prompt engineering, crossing our fingers, and hoping the model would return valid JSON. Then came JSON Mode, which improved things but still occasionally dropped keys or hallucinated data structures.
Now, OpenAI has introduced Structured Outputs, a feature that fundamentally changes the game by guaranteeing adherence to a provided JSON Schema. In this definitive guide, we will explore everything you need to know about Structured Outputs, Strict JSON mode, how constrained decoding works, and how to implement it in your applications.
To understand why Structured Outputs is such a monumental leap forward, we must first look back at how JSON generation in LLMs has evolved.
Return ONLY valid JSON. Do not include markdown formatting. Use the exact keys: 'name' and 'age'. to their prompts.
Despite our best efforts, the models would frequently prepend Here is the JSON you requested: or wrap the output in markdown code blocks. Worse yet, they would sometimes hallucinate new properties or return deeply nested structures that broke our parsing logic. Developers had to write complex regex patterns and fallback mechanisms to salvage the data.
response_format: { type: "json_object" }, widely known as JSON Mode. This was a massive improvement. It instructed the model's internal processing to ensure the final output was syntactically valid JSON. If you used our JSON Validator on the output, it would almost always pass syntax checks.
However, JSON Mode only guaranteed valid JSON, not schema-compliant JSON. The model could still invent new keys, omit required fields, or change data types (e.g., returning a string instead of an integer). You still needed robust validation logic on your backend to ensure the data matched your expectations.
strict: true inside the JSON Schema definition. This isn't just a strong suggestion to the model; it is a hard, mathematical constraint applied during the text generation process. When you use Structured Outputs, the model is physically incapable of generating JSON that does not match your schema.
Structured Outputs is a feature in the OpenAI API that guarantees the model will generate outputs matching a developer-supplied JSON Schema. You don't need to worry about missing keys, incorrect data types, or invalid nesting.
This feature is available in two primary places within the OpenAI API:
The magic behind Structured Outputs is a technique called constrained decoding. To understand constrained decoding, you have to understand how LLMs generate text.
LLMs generate text one token at a time. At each step, the model calculates the probability distribution across its entire vocabulary (often 100,000+ tokens) and samples the next token.
In standard generation, the model might choose a bracket {, then a quote ", then a letter n, and so on. But there is nothing stopping it from choosing a random word instead.
With constrained decoding, OpenAI converts your JSON Schema into a finite state machine (FSM) or a context-free grammar (CFG). At every single token generation step, the inference engine looks at the current state of the JSON being generated and determines exactly which tokens are valid according to your schema.
For example, if your schema requires the key "user_age" and the model has already output {" , the constrained decoding engine will artificially set the probability of every token except u (or user) to absolutely zero. The model is forced down a deterministic path that perfectly satisfies your structural requirements.
Adopting Structured Outputs with strict: true provides numerous advantages for production applications:
SyntaxError: Unexpected token again. The output is guaranteed to be parseable.Let's look at how to implement Structured Outputs in both Python and Node.js using their respective SDKs. OpenAI has made this incredibly easy by integrating tightly with popular validation libraries like Pydantic (Python) and Zod (Node.js).
Pydantic is the standard for data validation in Python. The OpenAI Python SDK allows you to pass a Pydantic model directly to the response_format argument.
import os
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
# 1. Define your schema using a Pydantic BaseModel
class UserProfile(BaseModel):
name: str
age: int
is_active: bool
hobbies: list[str]
# 2. Call the API using the beta.chat.completions.parse method
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract the user profile from the text."},
{"role": "user", "content": "John Doe is a 28-year-old software engineer who loves rock climbing and reading sci-fi. He is currently employed."}
],
# 3. Pass the Pydantic model here
response_format=UserProfile,
)
# 4. Access the safely parsed, strongly-typed object directly
user_profile = response.choices[0].message.parsed
print(user_profile.name) # Output: John Doe
print(user_profile.age) # Output: 28
print(user_profile.hobbies) # Output: ['rock climbing', 'reading sci-fi']
For JavaScript and TypeScript developers, Zod is the go-to library for schema validation. The OpenAI Node.js SDK integrates natively with Zod.
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';
const openai = new OpenAI();
// 1. Define your schema using Zod
const UserProfileSchema = z.object({
name: z.string(),
age: z.number(),
isActive: z.boolean(),
hobbies: z.array(z.string()),
});
async function extractProfile() {
// 2. Call the API using the parse method
const response = await openai.beta.chat.completions.parse({
model: 'gpt-4o-2024-08-06',
messages: [
{ role: 'system', content: 'Extract the user profile from the text.' },
{ role: 'user', content: 'Jane Smith is 32, inactive right now, but enjoys swimming and painting.' }
],
// 3. Pass the Zod schema wrapped in zodResponseFormat
response_format: zodResponseFormat(UserProfileSchema, 'user_profile'),
});
// 4. Access the parsed object
const profile = response.choices[0].message.parsed;
console.log(profile.name); // Output: Jane Smith
console.log(profile.hobbies); // Output: [ 'swimming', 'painting' ]
}
extractProfile();
If you prefer not to use Pydantic or Zod, you can pass a raw JSON Schema directly to the API. However, you must ensure that strict: true is set, and that all properties are marked as required. You can specify additionalProperties: false to ensure no extra keys are generated.
Here is an example of a raw JSON Schema payload:
{
"type": "json_schema",
"json_schema": {
"name": "product_extraction",
"strict": true,
"schema": {
"type": "object",
"properties": {
"product_name": { "type": "string" },
"price": { "type": "number" },
"in_stock": { "type": "boolean" }
},
"required": ["product_name", "price", "in_stock"],
"additionalProperties": false
}
}
}
When working with complex schemas, it is always a good practice to test them first. You can use our JSON Validator tool to ensure your raw schema is structurally sound before sending it to the OpenAI API.
While Structured Outputs is incredibly powerful, it does have some limitations that developers must be aware of:
strict: true is enabled, all fields defined in your schema must be included in the required array. The model is not allowed to optionally omit fields. If you want a field to be optional, you must explicitly allow null as a type (e.g., ["string", "null"]).additionalProperties: false. The model cannot invent new keys outside of the defined schema.string, number, boolean, object, and array are supported. However, advanced JSON Schema features like pattern (regex constraints), minLength, maxLength, or format (e.g., email, uri) are not supported by constrained decoding.It is common to get confused about when to use which feature. Here is a quick breakdown to help you decide:
response_format: { type: "json_object" }): Use this when you want the model to return JSON, but you don't have a rigid schema, or your schema requires dynamic keys that cannot be predefined. You must explicitly tell the model to return JSON in the system prompt.response_format: { type: "json_schema" }): Use this when you are extracting data into a fixed, known format (like populating a database row, returning data for a UI component, or generating configuration files). This is the safest and most robust option.To get the absolute best results when using Structured Outputs, follow these best practices:
{
"properties": {
"confidence_score": {
"type": "number",
"description": "A score between 0.0 and 1.0 indicating how confident the model is in the extraction."
}
}
}
refusal property on the response message before attempting to access the parsed data.
max_tokens limit, the generation will be cut off mid-stream. In this case, the finish_reason will be length, and the JSON will be incomplete and invalid. Always check the finish_reason in your production code.
The introduction of Strict JSON mode unlocks a myriad of enterprise-grade use cases that were previously too fragile to deploy confidently.
Even with Strict JSON mode, developers may encounter a few common hiccups:
additionalProperties must be false. Make sure your raw JSON schema explicitly sets additionalProperties: false at every object level.required array. If a field is optional, change its type to include null.pattern or minLength, the API will reject your request. Remove these constraints and handle that level of validation on your backend after receiving the response.The introduction of Structured Outputs marks a significant maturation point for Large Language Models. We are moving away from treating LLMs solely as conversational chatbots and increasingly utilizing them as deterministic data processing engines.
By marrying the reasoning capabilities of LLMs with the rigidity of strict schemas, developers can finally build AI applications that are both intelligent and reliable.
OpenAI's Structured Outputs and Strict JSON mode solve one of the biggest pain points in AI engineering. By leveraging constrained decoding, you can guarantee that the data flowing out of the model perfectly matches the data structures your application expects.
Whether you are using Pydantic in Python, Zod in Node.js, or raw JSON Schemas, migrating to Structured Outputs will make your applications faster, cheaper, and significantly more resilient.
Be sure to bookmark our JSON Validator to help you build and test your schemas before deploying them to production. Happy building!