JSON Schema to TypeScript
Paste a JSON Schema, get TypeScript types — local $ref and $defs resolved, every dropped keyword named, entirely on this device.
JSON Schema has no date type — `format: "date-time"` is an annotation, so those values stay `string` unless you opt in.
quicktype adds `[property: string]: unknown;` where a schema does not set `additionalProperties`. Strip removes those lines. When `additionalProperties` is itself a schema next to `properties`, the index signature and the named properties disagree and TypeScript reports error 2411 — the notes below say so when it happens.
The generated TypeScript appears here as you type.
Keywords that do not survive into a type
Everything below is checked at run time, not by the type system.
Press ? for shortcuts
Do it offline:$ npx quicktype schema.json --src-lang schema --lang typescript --just-types -o types.ts
How to use it
- Paste one schema document, or drop a .json file onto the input pane — a fresh set of declarations renders as you type.
- Read the dropped-keyword list under the output: each row gives a keyword and the JSON Pointer it came from.
- Name the top-level type, then choose interface or alias declarations, readonly members, date-time handling and index signatures.
- Copy the result, save it as .ts, or share a link that reopens this schema under the same option set.
Paste a JSON Schema and read TypeScript back: properties become members, the required array decides which of them carry the ? marker, and local $ref and $defs resolve into named declarations. Keywords only a runtime check can enforce — pattern, minimum, uniqueItems — are listed with the pointer they sat at instead of quietly disappearing. Interfaces or type aliases, your choice, generated here in this tab.
Examples
{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","required":["id"],"properties":{"id":{"type":"integer","minimum":1},"email":{"type":"string","format":"email","pattern":"^.+@.+$"}},"additionalProperties":false}export interface Root {
email?: string;
id: number;
}
Defaults. Only id is required, so email takes the ? marker; integer widens to number; minimum and pattern leave the type and come back in the dropped list with their pointers.
{"type":"object","required":["created"],"properties":{"created":{"type":"string","format":"date-time"}},"additionalProperties":false}export interface Root {
created: string; // ISO 8601 date-time
}
Set dates to iso-comment and a format keyword is documented rather than typed: the member stays string, and a marker records which shape of string the schema asks for.
{"type":"object","properties":{"child":{"$ref":"#"},"name":{"type":"string"}},"additionalProperties":false}export interface Root {
child?: Child;
name?: string;
}
export interface Child {
child?: Child;
name?: string;
}
Edge case. A $ref back to the document root makes the generator emit a second, structurally identical declaration named after the referring property. Delete Child and aim child at Root to keep one.
{"allOf":[{"type":"object","properties":{"a":{"type":"string"}},"additionalProperties":false},{"type":"object","properties":{"b":{"type":"number"}},"additionalProperties":false}]}export type Root = Record<string, never>;
Edge case, and a real loss: two allOf branches that each close with additionalProperties: false are not merged, so a and b both vanish. A chip says so. Write the members into one schema instead.
{"type":"object","required":["a"],"properties":{"a":{"type":"string"}},"additionalProperties":false}export type UserProfile = {
readonly a: string;
}
Alias declarations with readonly members, under a top-level name that was not a legal identifier: "User Profile" is legalized for the code and the original spelling is reported.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Validation keywords are named, never silently discarded
pattern, minimum, multipleOf, maxLength, uniqueItems, dependentRequired, if/then/else and their neighbours constrain values, and no static type can carry them. Each is reported against the pointer it sat at, so the rules you still have to check at runtime end up written down.
integer arrives as number
TypeScript has a single numeric type, so a field declared integer is typed number and whole-number-ness stops being a compiler concern. Every pointer where that widening happened is listed; a runtime guard is the way back.
prefixItems tuples flatten into one element type
A two-position tuple of a string then a number renders as Array<number | string>: the generator emits no tuple types, so membership survives and position does not. Where order carries meaning, replace that member by hand.
$defs keys do not reach the generated code
A subschema under $defs is named after the first property that references it, so $defs.Address reached through addr is declared as Addr. Entries in the legacy definitions container keep their key. Either way a chip lists what was renamed.
The index signature is the open-object reading
A schema that omits additionalProperties accepts unknown keys, and that permission is rendered as an unknown-valued index signature. Strip it when you want the closed reading in code — though the schema keeps accepting whatever it accepted before.
Common errors
Unexpected '}' at line 1, column 19 — probable cause: trailing comma after the last property.- The document is parsed strictly before any generation is attempted, because the generator's own reply to malformed text names no position at all. Line, column and a framed excerpt point at the character to repair.
Remote reference at /properties/user: this tool never fetches schemas over the network — inline the referenced schema under $defs and try again.- A $ref pointing at another document is refused before generation begins, so no request is ever contemplated. Paste the referenced subschema into $defs and aim the pointer at it; multi-file registries are out of scope here.
Subschema at /properties/never is false: a false schema accepts no value at all, so there is no TypeScript type that can describe it — remove the subschema or replace it with a real one and try again.- A bare false in a property position means nothing validates there, which no type can express. false in its ordinary closing positions is untouched and generates normally: additionalProperties, unevaluatedProperties, and items beside prefixItems all keep working.
Input is larger than the 1 MB limit for this tool.- A schema is a document rather than a data dump, so the ceiling is one megabyte and an oversized paste is declined whole instead of being cut into a shape nobody wrote. The printed command runs the same generator over a file of any size.
FAQ
Which keywords actually become part of the type?
type, properties, required, items, prefixItems, enum, const, $ref, $defs, allOf, oneOf and anyOf shape the declarations. The validation vocabulary — lengths, ranges, patterns, uniqueness, conditional subschemas — does not, because a type describes structure while those describe values. The dropped list draws that boundary for the document you pasted.
Why does a value my schema rejects still compile?
Because the declarations carry structure only. A field with minimum: 1 is generated as id: number, and { id: 0 } assigned to that type passes the compiler while a validator would reject it. That divergence is expected: the schema stays the runtime authority, and these declarations are the editor-time half.
Are references to other files resolved?
No. Pointers inside the pasted document resolve, whether they live under $defs or the legacy definitions container; anything addressing another document is refused by name before generation starts, so nothing is requested over the network. Inline the target subschema to generate against it.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Regenerate now, without waiting for the typing pause.
- ⌘/Ctrl + S
- Save the generated declarations as a .ts file.
- ⌘/Ctrl + C
- Copy the whole output while no text is selected.
- ⌘/Ctrl + Shift + C
- Copy the whole output even with a selection active.
- t
- Switch the color theme, except while a field has focus.
- ? or ⌘/Ctrl + /
- Show this list with your own platform's glyphs.
- Esc
- Close the open dialog, leaving the schema as it is.