Back to blog
Engineering24 min read

Free JSON Prompt Generator (Schema In, Valid JSON Out)

Seven copy-paste JSON schemas by job, the exact request body for OpenAI, Anthropic and Google, and the plain-prompt fallback for chat windows where nothing is enforced.

NH
Nafiul Hasan
Founder, Prompt Architects

TL;DR: A JSON prompt generator has to produce two things, not one: a JSON Schema, and the request body that carries it. On OpenAI, Anthropic and Google the schema is enforced during decoding, so the shape is guaranteed. In a chat window nothing is enforced, so you need a written contract plus your own validator.

What does a JSON prompt generator actually have to produce?

Two artefacts. A schema that describes the shape you want, and the request body that makes the model obey it. Most pages that call themselves a JSON prompt generator produce only the first, which is why the output still comes back wrapped in a code fence with a sentence of preamble in front of it.

The schema alone is a wish. The schema attached to the right parameter is a constraint. That distinction is the whole page, and it splits your options cleanly in two.

If you are calling an API, all three major providers now ship schema enforcement, and you should use it. If you are working in the ChatGPT, Claude or Gemini consumer apps, there is no schema parameter to reach for, and the honest answer is a strict output contract plus a validator on your side. Both paths are below, with everything ready to paste.

If you want the argument for why prose instructions lose to enforcement, that lives in the sibling post on why AI ignores your format instructions. This page assumes you are already convinced and want the artefacts.

Does a schema guarantee valid JSON?

On the enforced path, yes for shape. OpenAI's own wording is the clearest statement anyone publishes: Structured Outputs is a feature that "ensures the model will always generate responses that adhere to your supplied JSON Schema, so you don't need to worry about the model omitting a required key, or hallucinating an invalid enum value."

Compare that to the older JSON mode on the same page, which "will not guarantee the output matches any specific schema, only that it is valid and parses without errors" (developers.openai.com, read August 27, 2026). One promises your keys. The other promises only that a parser will not throw. If you have a downstream consumer expecting field names, JSON mode is not the feature you want.

Anthropic describes the same category of thing in mechanical terms rather than promises: structured outputs "guarantee schema-compliant responses through constrained decoding" (platform.claude.com, read August 27, 2026). Google's framing is the plainest of the three: "You can configure Gemini models to generate responses that adhere to a provided JSON Schema" (ai.google.dev, read August 27, 2026).

Enforcement surfaces per vendor, from each vendor's own structured-output documentation, read August 27, 2026.
FeatureOpenAIAnthropicGoogleChat window
Schema-enforced modeStructured OutputsJSON outputsresponse_format
Parametertext.formatoutput_config.formatresponse_formatNone
Generally available, no beta headerN/A
Every field must be requiredN/A
additionalProperties false requiredOptionalN/A
Numeric min and maxN/A
Key order follows schemaRequired keys firstNot documentedN/A

How do you send a schema to each provider?

Three parameter names, one idea. Each block below is a complete request you can run after exporting your key. The schema is deliberately identical in intent so you can see exactly where the dialects diverge.

# OpenAI — Structured Outputs on the Responses API
curl https://api.openai.com/v1/responses \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6",
    "input": [
      {"role": "developer", "content": "Extract the contact from the message."},
      {"role": "user", "content": "Hi, Dana Okafor here from Larkfield Dental, 0161 496 0022."}
    ],
    "text": {
      "format": {
        "type": "json_schema",
        "name": "contact",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "full_name": { "type": "string" },
            "organisation": { "type": ["string", "null"] },
            "phone": { "type": ["string", "null"] }
          },
          "required": ["full_name", "organisation", "phone"],
          "additionalProperties": false
        }
      }
    }
  }'
# Anthropic — JSON outputs on output_config.format, no beta header
curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Extract the contact: Dana Okafor, Larkfield Dental, 0161 496 0022."}
    ],
    "output_config": {
      "format": {
        "type": "json_schema",
        "schema": {
          "type": "object",
          "properties": {
            "full_name": { "type": "string" },
            "organisation": { "type": ["string", "null"] },
            "phone": { "type": ["string", "null"] }
          },
          "required": ["full_name"],
          "additionalProperties": false
        }
      }
    }
  }'
# Google — response_format on the interactions endpoint
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.7-flash",
    "input": "Extract the contact: Dana Okafor, Larkfield Dental, 0161 496 0022.",
    "response_format": {
      "type": "text",
      "mime_type": "application/json",
      "schema": {
        "type": "object",
        "properties": {
          "full_name": { "type": "string", "description": "Person name as written." },
          "organisation": { "type": ["string", "null"] },
          "phone": { "type": ["string", "null"] }
        },
        "required": ["full_name"]
      }
    }
  }'

Look at the required arrays. OpenAI needs all three fields listed, because its rule is absolute: "To use Structured Outputs, all fields or function parameters must be specified as required." An optional field is expressed by widening the type instead, since OpenAI notes "it is possible to emulate an optional parameter by using a union type with null". Anthropic and Google both accept a shorter required list, which is why the same schema needs three variants.

Which schema keywords survive on which provider?

Fewer than you expect, and the gaps do not overlap. This is the table that stops a schema working on Monday and failing on Tuesday when someone swaps the model behind a feature flag.

Schema keywordOpenAIAnthropicGoogle
enumSupportedSupported, casing not guaranteedSupported
pattern (regex)SupportedSupported, no backreferences or lookaroundNot listed
format for strings9 named formats9 named formatsdate-time, date, time
minimum / maximumSupportedExplicitly not supportedSupported
minLength / maxLengthNot in supported listExplicitly not supportedNot listed
minItems / maxItemsSupportedminItems of 0 or 1 onlySupported
$ref and $defsSupportedSupported, external refs excludedNot listed
anyOfSupportedSupportedNot listed
allOfNot supportedSupported, not with $refNot listed
Recursive schemasSupported via $defsNot supportedNot listed

Compiled from OpenAI's supported-schemas section, Anthropic's JSON Schema limitations accordion, and Google's JSON schema support section, all read August 27, 2026. "Not listed" means the vendor's own support list does not name the keyword, which is not the same as a documented rejection.

Three practical consequences. Anthropic states that "If you use an unsupported feature, you'll receive a 400 error with details", so a schema carrying maxLength fails loudly rather than silently. OpenAI caps complexity at a level most people never approach: "A schema may have up to 5000 object properties total, with up to 10 levels of nesting." Google is vaguer and warns that "Very large or deeply nested schemas may be rejected."

Key ordering differs too, and it matters if you diff outputs. OpenAI says "outputs will be produced in the same order as the ordering of keys in the schema". Anthropic keeps schema order with one twist: "required properties appear first, followed by optional properties". If stable ordering matters to you on Claude, mark everything required.

Seven JSON schemas, by job

Each pair below is a schema plus the instruction text that goes alongside it. The schemas are written in the strictest dialect, meaning every field required and additionalProperties set to false, because that version runs unchanged on OpenAI and needs only deletions elsewhere. Optional fields are expressed as null unions.

Extraction

The most common job, and the one where null discipline earns its keep. An extractor that invents a phone number is worse than one that returns null.

{
  "type": "object",
  "properties": {
    "document_type": { "type": "string", "enum": ["invoice", "receipt", "purchase_order", "other"] },
    "supplier_name": { "type": "string" },
    "invoice_number": { "type": ["string", "null"] },
    "issue_date": { "type": ["string", "null"], "format": "date" },
    "currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },
    "total_amount": { "type": ["number", "null"] },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "quantity": { "type": ["number", "null"] },
          "unit_price": { "type": ["number", "null"] }
        },
        "required": ["description", "quantity", "unit_price"],
        "additionalProperties": false
      }
    },
    "fields_not_found": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["document_type", "supplier_name", "invoice_number", "issue_date",
               "currency", "total_amount", "line_items", "fields_not_found"],
  "additionalProperties": false
}
Extract the fields defined by the schema from the document below.

Rules:
- Copy values exactly as written in the source. Do not reformat numbers or names.
- If a value is absent, set it to null AND add the field name to fields_not_found.
- Never infer a value from context. Absence is a valid answer.
- total_amount is the final payable figure, not a subtotal.

<document>
{{DOCUMENT}}
</document>

The fields_not_found array is the part worth stealing. It gives the model an explicit place to record a gap, which measurably reduces the pressure to fill one in. OpenAI recommends the same move for exactly this reason, suggesting you "include language in your prompt to specify that you want to return empty parameters" when input may not fit the task.

Classification

Enums are the point here. The whole reason to enforce a schema on a classifier is that the label set becomes closed.

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "bug_report", "feature_request", "account_access", "other"]
    },
    "urgency": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "evidence_quote": { "type": "string" },
    "needs_human": { "type": "boolean" }
  },
  "required": ["category", "urgency", "confidence", "evidence_quote", "needs_human"],
  "additionalProperties": false
}
Classify the support message using ONLY the categories in the schema.

- evidence_quote must be a verbatim span copied from the message that justifies
  the category. If no span justifies it, choose "other".
- confidence is your own estimate, not a formality. Below 0.6, set needs_human true.
- urgency reflects business impact, not the customer's tone.

<message>
{{MESSAGE}}
</message>

Two caveats before you ship this. On Anthropic, drop minimum and maximum, which are not supported, and clamp the number yourself. And on any vendor, compare labels case-insensitively: Anthropic documents that "Structured outputs don't guarantee the capitalization of string enum and const values", advising you to "Compare enum values case-insensitively, and avoid enum values that differ only in capitalization."

Structured summary

Summaries are where schemas quietly do the most work, because they force the model to separate claim types instead of blending them into a paragraph.

{
  "type": "object",
  "properties": {
    "headline": { "type": "string" },
    "decisions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "decision": { "type": "string" },
          "owner": { "type": ["string", "null"] },
          "due_date": { "type": ["string", "null"], "format": "date" }
        },
        "required": ["decision", "owner", "due_date"],
        "additionalProperties": false
      }
    },
    "open_questions": { "type": "array", "items": { "type": "string" } },
    "risks": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "risk": { "type": "string" },
          "severity": { "type": "string", "enum": ["low", "medium", "high"] }
        },
        "required": ["risk", "severity"],
        "additionalProperties": false
      }
    },
    "not_discussed": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["headline", "decisions", "open_questions", "risks", "not_discussed"],
  "additionalProperties": false
}
Summarise the transcript into the schema.

- A decision is something the group settled. A preference someone expressed is
  not a decision; it belongs in open_questions.
- owner is a named person from the transcript or null. Never "the team".
- not_discussed lists any agenda item from the input that never came up.
- Every array may be empty. An empty array is a correct answer.

<transcript>
{{TRANSCRIPT}}
</transcript>

Form filling

The job where the schema is not yours to invent, because a form already defines it. Model the validation rules in the schema wherever the vendor supports them, and repeat them in prose where it does not.

{
  "type": "object",
  "properties": {
    "applicant_name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "postcode": { "type": ["string", "null"], "pattern": "^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$" },
    "years_experience": { "type": ["integer", "null"], "minimum": 0, "maximum": 60 },
    "role_applied_for": { "type": "string", "enum": ["engineer", "designer", "analyst", "other"] },
    "right_to_work_stated": { "type": "boolean" },
    "source_span": { "type": "string" }
  },
  "required": ["applicant_name", "email", "postcode", "years_experience",
               "role_applied_for", "right_to_work_stated", "source_span"],
  "additionalProperties": false
}
Fill the form fields from the applicant's message. This is a transcription task,
not an assessment task.

- source_span: quote the fragment of the message you drew the answer from.
- right_to_work_stated is true only if the applicant explicitly states it.
  Silence is false, not true.
- If the postcode is malformed, return null rather than repairing it.

<message>
{{MESSAGE}}
</message>

Data cleaning

Normalisation prompts fail in a specific way: the model silently improves things you did not ask it to touch. The schema fixes that by making the original and the cleaned value both first-class fields.

{
  "type": "object",
  "properties": {
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "row_id": { "type": "integer" },
          "original_value": { "type": "string" },
          "cleaned_value": { "type": ["string", "null"] },
          "transformation": {
            "type": "string",
            "enum": ["unchanged", "trimmed", "case_normalised", "date_reformatted",
                     "unit_converted", "split", "rejected"]
          },
          "note": { "type": ["string", "null"] }
        },
        "required": ["row_id", "original_value", "cleaned_value", "transformation", "note"],
        "additionalProperties": false
      }
    }
  },
  "required": ["rows"],
  "additionalProperties": false
}
Normalise each row against the target format below. Return one object per input
row, in input order, with the same row_id.

Target: dates as YYYY-MM-DD, currency as a bare number, names as Title Case.

- Always echo original_value verbatim so the change is auditable.
- Use "rejected" with cleaned_value null when the input is unrecoverable.
- Use "unchanged" when the value already matches the target. Do not polish it.

<rows>
{{ROWS}}
</rows>

Evaluation rubric

Judge prompts are the highest-value place to enforce a schema, because the output is consumed by a script rather than read by a person.

{
  "type": "object",
  "properties": {
    "criteria": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "enum": ["accuracy", "completeness", "tone", "format_compliance"] },
          "score": { "type": "integer", "minimum": 1, "maximum": 5 },
          "justification": { "type": "string" },
          "failing_span": { "type": ["string", "null"] }
        },
        "required": ["name", "score", "justification", "failing_span"],
        "additionalProperties": false
      }
    },
    "overall": { "type": "string", "enum": ["pass", "borderline", "fail"] },
    "single_worst_problem": { "type": ["string", "null"] }
  },
  "required": ["criteria", "overall", "single_worst_problem"],
  "additionalProperties": false
}
Score the candidate output against the rubric. Return exactly four criteria
objects, one per enum value, in schema order.

Scoring anchors: 1 = unusable, 3 = usable with edits, 5 = shippable as-is.

- justification must cite something specific. "Good overall" is not a justification.
- failing_span quotes the exact text that lost the point, or null at score 5.
- overall is "fail" if any criterion scores 1 or 2, regardless of the others.

<candidate>
{{CANDIDATE}}
</candidate>

API-shaped response

The envelope pattern. Useful whenever the model sits behind an endpoint of your own and callers need a predictable failure shape as well as a predictable success shape.

{
  "type": "object",
  "properties": {
    "status": { "type": "string", "enum": ["ok", "partial", "refused", "insufficient_input"] },
    "data": {
      "type": ["object", "null"],
      "properties": {
        "summary": { "type": "string" },
        "tags": { "type": "array", "items": { "type": "string" } }
      },
      "required": ["summary", "tags"],
      "additionalProperties": false
    },
    "error": {
      "type": ["object", "null"],
      "properties": {
        "code": { "type": "string", "enum": ["missing_field", "ambiguous_input", "out_of_scope"] },
        "message": { "type": "string" }
      },
      "required": ["code", "message"],
      "additionalProperties": false
    },
    "model_notes": { "type": ["string", "null"] }
  },
  "required": ["status", "data", "error", "model_notes"],
  "additionalProperties": false
}
Respond in the envelope schema.

- status "ok" requires data non-null and error null.
- status "insufficient_input" requires error non-null and data null. Use it when
  the input does not contain enough to answer. This is a success, not a failure.
- Never populate both data and error.
- model_notes is for caveats a human reviewer should see. Null when there are none.

<input>
{{INPUT}}
</input>

What do you do when there is no schema parameter?

Write the contract explicitly and validate on your side, because the consumer apps do not expose one. ChatGPT, Claude and Gemini in a browser tab give you a text box, not a response_format field, so everything above degrades to a strongly worded request.

The two habits that make the degraded path survivable are a contract block and a defensive parse. Here is the contract block, and it is the single most reusable artefact on this page.

<output_contract>
Return ONE JSON document and nothing else.
The first character of your reply is { and the last character is }.
No preamble. No explanation. No markdown code fence. No trailing commentary.

SHAPE:
{
  "field_one": string,
  "field_two": string | null,
  "field_three": ["low" | "medium" | "high"]
}

RULES:
- Every key above must be present. Use null for unknown, never omit the key.
- Never add keys that are not listed.
- Strings are copied from the input, not paraphrased.
- If the input cannot produce a valid document, return every value as null.
</output_contract>

Paste that above your task, and the failure rate drops sharply. It does not go to zero, and any page that tells you otherwise is selling something. Anthropic explicitly rules out the old workaround: message prefilling is listed as incompatible with JSON outputs, and separately, "Starting with Claude 4.6 models and Claude Mythos Preview, prefilled responses (providing a partial assistant message for Claude to continue from) on the last assistant turn are no longer supported." Those requests "return a 400 error", though "Earlier models continue to support prefills, and adding assistant messages elsewhere in the conversation is not affected" (platform.claude.com, read August 27, 2026).

The other stale reflex is worth naming while we are here. Dropping the temperature was the standard advice for reliable JSON, and on current Claude models it is not available: Anthropic's Messages API reference marks the parameter deprecated and states that "Models released after Claude Opus 4.6 do not support setting temperature." Sampling was never a formatting control anyway.

Here is a full chat-window prompt using the contract, for an extraction task with no enforcement available.

You are a data extractor. Read the email and produce a contact record.

<output_contract>
Return ONE JSON document and nothing else.
First character { and last character }.
No preamble, no code fence, no commentary.

SHAPE:
{
  "full_name": string,
  "organisation": string | null,
  "phone": string | null,
  "email": string | null,
  "fields_not_found": [string]
}

RULES:
- Copy values verbatim. Do not reformat phone numbers.
- Unknown values are null AND listed in fields_not_found.
- Never guess from context.
</output_contract>

<email>
{{EMAIL}}
</email>

Should the schema itself be JSON inside the prompt?

Usually not, and this is the counterintuitive half. JSON is an excellent output format and a mediocre input format, and OpenAI publishes evidence for exactly that.

Its GPT-4.1 prompting guide recommends markdown as the default delimiter, notes that JSON "can be more verbose, and require character escaping that can add overhead", and reports flatly from long-context testing that for stuffing many documents into a prompt, "JSON performed particularly poorly" (developers.openai.com, read August 27, 2026).

So put the machine-readable schema where a machine reads it, in the API parameter, and describe the shape to the model in the lightest legible form. The contract block above uses a JSON-ish sketch rather than a full JSON Schema document for that reason: it is shorter, it escapes nothing, and it says the same thing. If you want the fuller argument for picking a shape before you commit to one, we wrote it up in how to decide what output format you actually need, and the concept-level treatment of JSON prompting lives in JSON prompts explained.

One genuine cost of enforcement, from Anthropic, since nobody else mentions it: "The first time you use a specific schema, there is additional latency while the grammar compiles". After that, "Compiled grammars are cached for 24 hours from last use, making subsequent requests much faster". If you generate a fresh schema per request, you pay that compile every time.

Does valid JSON mean correct JSON?

No, and this is the ceiling every generator page should state and almost none do. Schema enforcement guarantees the container. It says nothing about the contents.

OpenAI is candid about it in its own documentation: "Structured Outputs can still contain mistakes." Worse, the constraint can actively produce them, because "The model will always try to adhere to the provided schema, which can result in hallucinations if the input is completely unrelated to the schema." A required string field is a required string field, so the model will produce a string, whether or not the document contains one.

Google says the same thing as an instruction rather than a caveat: "While output is syntactically correct JSON, always validate values in your application", and asks you to "Implement robust error handling for schema-compliant but semantically incorrect outputs."

Two schema-level habits reduce the damage. First, make absence expressible: a nullable field plus a fields_not_found array gives the model somewhere honest to go. Second, make claims traceable: an evidence_quote or source_span field that must be copied verbatim from the input turns an unverifiable assertion into something you can check with a substring match, in code, at zero cost.

Two more failure modes return a response that does not match your schema at all, and both are documented rather than theoretical. Anthropic returns stop_reason: "refusal" for a safety refusal, with a 200 status code and billed tokens. And when a response hits the ceiling, it returns stop_reason: "max_tokens", where the output may be incomplete. Handle both before you call JSON.parse.

How do you test a schema before you ship it?

Run it against the ugly inputs first, not the clean one. A schema that works on a tidy example and collapses on a real one is the normal outcome, because you designed it while looking at the tidy example.

// Minimal harness: validate shape, then check the claims are grounded.
import Ajv from "ajv";
import addFormats from "ajv-formats";

const ajv = addFormats(new Ajv({ allErrors: true, strict: false }));

export function auditOutput(raw, schema, sourceText) {
  const FENCE = /^\s*`{3}(?:json)?\s*|\s*`{3}\s*$/g;
  const cleaned = raw.replace(FENCE, "").trim();

  let doc;
  try { doc = JSON.parse(cleaned); }
  catch (e) { return { ok: false, stage: "parse", detail: e.message }; }

  const validate = ajv.compile(schema);
  if (!validate(doc)) {
    return { ok: false, stage: "schema", detail: validate.errors };
  }

  // Shape passed. Now check the model did not invent its own evidence.
  const quoted = collectSpans(doc, ["evidence_quote", "source_span"]);
  const ungrounded = quoted.filter((q) => q && !sourceText.includes(q));
  if (ungrounded.length) {
    return { ok: false, stage: "grounding", detail: ungrounded };
  }

  return { ok: true, value: doc };
}

function collectSpans(node, keys, out = []) {
  if (Array.isArray(node)) node.forEach((n) => collectSpans(n, keys, out));
  else if (node && typeof node === "object") {
    for (const [k, v] of Object.entries(node)) {
      if (keys.includes(k) && typeof v === "string") out.push(v);
      else collectSpans(v, keys, out);
    }
  }
  return out;
}

The grounding check at the end of that function is the part worth keeping. A verbatim-span field plus a substring test catches the exact class of error that schema validation cannot: correctly typed, correctly named, entirely fabricated. It costs one string comparison.

Run the same fixtures through all three providers if you plan to switch between them, because the keyword table above means a schema can be valid on one and a 400 on another. Below is the shim that keeps one canonical schema and emits the three request bodies.

import copy

def strip_unsupported(schema, vendor):
    """One canonical schema in, one vendor-legal schema out."""
    s = copy.deepcopy(schema)

    def walk(node):
        if isinstance(node, dict):
            if vendor == "anthropic":
                for k in ("minimum", "maximum", "multipleOf", "minLength", "maxLength", "maxItems"):
                    node.pop(k, None)
                if node.get("minItems") not in (0, 1, None):
                    node.pop("minItems", None)
            if vendor == "openai" and node.get("type") == "object":
                node["additionalProperties"] = False
                node["required"] = list(node.get("properties", {}).keys())
            for v in node.values():
                walk(v)
        elif isinstance(node, list):
            for v in node:
                walk(v)

    walk(s)
    return s


def request_body(schema, vendor, model, user_text):
    s = strip_unsupported(schema, vendor)
    if vendor == "openai":
        return {"model": model, "input": [{"role": "user", "content": user_text}],
                "text": {"format": {"type": "json_schema", "name": "result",
                                    "strict": True, "schema": s}}}
    if vendor == "anthropic":
        return {"model": model, "max_tokens": 2048,
                "messages": [{"role": "user", "content": user_text}],
                "output_config": {"format": {"type": "json_schema", "schema": s}}}
    if vendor == "google":
        return {"model": model, "input": user_text,
                "response_format": {"type": "text", "mime_type": "application/json",
                                    "schema": s}}
    raise ValueError(vendor)

The five-slot generator, and where to keep it

Every schema on this page came out of the same five questions. Answer them and the schema writes itself, which is what a generator is actually doing under the interface.

  1. What is the unit? One object, or an array of them. Getting this wrong is the most common cause of a schema that fights the task.
  2. Which fields are genuinely required? Everything else is a null union. On OpenAI, all of them go in required regardless, and the nullability lives in the type.
  3. Which fields are closed sets? Those become enums, and enums are where enforcement pays for itself.
  4. How does the model say "I don't know"? A null plus a fields_not_found entry. Without this slot the model fills the gap.
  5. How do you check a claim later? A verbatim span field, tested with a substring match.
<schema_brief>
UNIT: [one object | array of objects] representing [thing]
REQUIRED: [field (type), field (type)]
OPTIONAL: [field (type or null)]
CLOSED SETS: [field: value | value | value]
ABSENCE: unknown values are null and listed in fields_not_found
TRACEABILITY: [field] holds a verbatim span copied from the input
FORBIDDEN: additional keys, prose, code fences
</schema_brief>

Paste that brief into any capable model and you get a first-draft JSON Schema back in seconds. Then do the part nobody does: save it. A schema is a durable asset that outlives the conversation it was written in, and rewriting the same extraction contract every fortnight is the actual cost of not having a library.

That is the honest version of where our own product fits. Prompt Architects generates schema-aware JSON prompts and stores them with variables, so the contract becomes reusable rather than retyped. It is not free at that tier: JSON prompt support sits on the Advanced and Team plans and the pricing comparison shows an X against Pro. The free plan publishes five prompt enhancements per day, which is enough to draft and refine the prose half of a schema prompt but does not include JSON mode. Everything on this page works without any of that, which is rather the point of publishing it.

Free Chrome Extension

Stop rewriting prompts. Start shipping.

Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 5.0★ on the Chrome Web Store.

Create An Account

Frequently asked questions

Free Chrome Extension

Stop rewriting prompts. Start shipping.

Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 5.0★ on the Chrome Web Store.

Create An Account