TypeScript to JSON Schema
Turn TypeScript interfaces and type aliases into a JSON Schema or Zod schema — unsupported types are named, never guessed — entirely on this device.
The root type is written out at the top level; every other declared type lands in the definitions section and is referenced by $ref.
Reject sets additionalProperties: false on every generated object. An index signature in the source overrides this choice for the object that declares it, and the note says so.
Import style applies to Zod output only.
Paste TypeScript on the left and the schema appears here.
npx ts-json-schema-generator -p input.ts -t RootNotes about this schema:
Press ? for shortcuts
Do it offline:$ npx ts-json-schema-generator -p input.ts -t Root
How to use it
- Paste the declarations, or drop a .ts file on the left pane. The first run pulls the TypeScript compiler into this tab — around 1.65 MB compressed, on a progress row you can cancel.
- Pick the root type if the file declares several; everything it reaches is published beside it under its own name.
- Choose the dialect, decide whether unknown keys pass, and switch to Zod when you want a parser instead of a document.
- Read the notes under the panes: each names something the compiler could not resolve, with the line and column it sits on.
Types vanish when TypeScript compiles, so nothing checks the JSON arriving at runtime. Paste your interfaces, type aliases and enums here and read back a JSON Schema — or Zod source — that says the same thing to a validator. Members become properties, the ? marker decides what is required, and anything this page cannot resolve without a type checker is named with its line rather than guessed at.
Examples
export interface User {
id: number;
email?: string;
tags: string[];
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User",
"type": "object",
"properties": {
"id": {
"type": "number"
},
"email": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"id",
"tags"
],
"additionalProperties": false
}
Defaults. The ? on email is the whole difference between the two required entries and the third, and number covers every numeric type TypeScript has, so nothing promises a whole number.
interface Box<T> {
value: T;
}
interface Summary {
picked: Pick<User, "id">;
user: User;
}
interface User {
id: number;
name: string;
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Summary",
"type": "object",
"properties": {
"picked": true,
"user": {
"$ref": "#/$defs/User"
}
},
"required": [
"picked",
"user"
],
"additionalProperties": false,
"$defs": {
"User": {
"title": "User",
"type": "object",
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
}
}
Edge case. Two constructs need a type checker: the generic Box at 1:1 and the Pick at 5:11. Both are reported there, picked is left open rather than invented, and the plain User reference resolves.
interface Node {
value: string;
children: Node[];
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/Node",
"$defs": {
"Node": {
"title": "Node",
"type": "object",
"properties": {
"value": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"$ref": "#/$defs/Node"
}
}
},
"required": [
"value",
"children"
],
"additionalProperties": false
}
}
}
A type that mentions itself moves into the definitions container and the document points at it, so a validator follows the same loop the data takes rather than some fixed depth.
type Pair = [string, number?, ...boolean[]];
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Pair",
"type": "array",
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"additionalItems": {
"type": "boolean"
},
"minItems": 1
}
Draft-07 spells fixed positions as an array under items and the rest under additionalItems. Only the first slot is mandatory, so minItems is 1; on 2020-12 the same source uses prefixItems.
enum Color {
Red = "red",
Green = "green",
}
enum Level {
Low,
High = 5,
Higher,
}
interface Item {
color: Color;
level: Level;
}
import * as z from "zod";
export const ColorSchema = z.enum([
"green",
"red",
]);
export type Color = z.infer<typeof ColorSchema>;
export const ItemSchema = z.object({
"color": ColorSchema,
"level": z.number(),
});
export type Item = z.infer<typeof ItemSchema>;
Zod mode, rooted at Item. The string enum survives as a fixed value list; the numeric one arrives as z.number(), losing the members 0, 5 and 6 that the JSON Schema output does keep.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Three ways to say a member might be missing
A ? leaves the member out of the required list. A union with undefined keeps it required and reports the undefined as dropped, JSON having no such value. Adding null differs again: still required, but the type becomes a list of two.
Tuples are spelled differently in each dialect
Positions become prefixItems on 2020-12 and an items array on the older drafts; a rest element becomes the single schema covering everything after them, and length then has no ceiling.
An index signature outranks the unknown-key switch
Declare [key: string]: number and that object takes any key whose value fits, whatever the closed or open choice says; a note names the position, so the override is never silent. Numeric index signatures are reported instead.
Anything needing the checker is named, not invented
Generic declarations, utility types like Partial and Omit, mapped, conditional and template-literal types, classes, functions and imported types all need resolution this page does not perform. Each is reported by identifier, line and column, and its position then accepts any value.
Nothing is fetched, so Date is not built in either
One file is read and no module resolution runs, so a name it does not declare stays unresolved — library types such as Date included. Write the shape you send, an ISO string say, as its own member.
Comments carry no constraints
JSDoc above a member is dropped and reported dropped. Tags such as @minimum or @format that other generators read are ignored, so a schema never gains a rule with no counterpart in your declarations.
Common errors
Property or signature expected. (3:3)- The compiler's own wording, with the position it reported and a framed excerpt. Parsing must succeed before anything is emitted, so a stray brace stops the run instead of half-writing a document.
No interface, type alias or enum was found in this file. Paste a type declaration — this page reads declarations, it does not read values.- A valid file declaring only constants, functions or classes gives this page nothing to describe. A pasted JSON payload usually trips the parse error above first — sample data is another tool's input.
Input is larger than the 1 MB limit for this tool.- Source is a document rather than a data dump, so the ceiling is one megabyte and an oversized paste is declined whole rather than cut somewhere nobody chose. The printed command has no such limit.
FAQ
Does this type-check the code I paste?
No. The file is parsed, never checked: syntax errors come back in the compiler's own words and position, while anything needing type resolution is listed as unsupported. That is also why an import cannot be followed — only the text in the pane exists here.
Why did my Partial<User> member end up accepting anything?
Utility types are computed by the type checker, which would mean resolving the whole program. Rather than guess, that position is emitted as a schema accepting any value, and a note records the identifier with its coordinates. Write the resulting shape out as its own interface to get the real thing.
Can I run the Zod output as it stands?
It is source text using z.object, z.array, z.union, z.enum and z.infer, generated from the schema just built here and checked for parse errors only. Zod is not installed on this page: install it in your project and let your compiler have the last word.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Generate now, skipping the typing pause.
- ⌘/Ctrl + S
- Save the generated output to a file.
- ⌘/Ctrl + C
- Copy the output when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy the output despite a selection.
- t
- Change the colour theme, unless a field has focus.
- ? or ⌘/Ctrl + /
- List these keys with your platform's glyphs.
- Esc
- Dismiss the open dialog; the source stays.