What this tool does
Generate TypeScript interfaces from a JSON sample. Paste a payload — an API response, a config blob, a webhook body — and get strongly-typed interface declarations you can drop into a project. Array element types are inferred, nested objects become their own interfaces, nullable fields are emitted as a union with null. Powered by quicktype, runs entirely in your browser; no payload leaves the page.
How to use it
Paste JSON (or load the example), pick a top-level type name, and read the TypeScript on the right. Switch to a different language at any time via the tabs above — the same input regenerates in place.
Input: {"id":42,"name":"devsmiths","createdAt":"2024-03-11T08:24:00Z","stars":1280,"public":true,"contributors":[{"login":"ada","commits":51,"admin":true},{"login":"linus","commits":33,"admin":false}],"homepage":null}
Output (TypeScript):
export interface Root {
id: number;
name: string;
createdAt: string;
stars: number;
public: boolean;
contributors: Contributor[];
homepage: null;
}
export interface Contributor {
login: string;
commits: number;
admin: boolean;
}Limits and edge cases
- Output uses
interfaceby default. If you need atypealias instead (e.g. for union types or mapped types), toggle just types in the options pane —typeoutput is closer to a structural definition and lets you compose with utility types later. - Fields where the sample contains
nullare typed as the literalnull, notnull | string. The generator has only one sample to work from — it doesn’t know the field is sometimes a string. Widen by hand, or pass all optional in the options pane. - TypeScript output is the only target with full CodeMirror syntax highlighting. Other languages render as plain monospace text to keep the page bundle small; Copy / Download still produce correct files.
- The generator does not emit runtime validators (no Zod, Valibot, or io-ts). If you need to validate untrusted JSON at runtime, generate the interfaces here and write a validator alongside, or use a schema-first tool like quicktype with Zod output.
- Single-sample inference: every field present in your sample becomes part of the type. Fields absent from the sample don’t become optional automatically — toggle all optional if every field should be nullable.
- Background reading: JSON to TypeScript in practice walks through the rename / tighten step end-to-end.
Frequently asked questions
- Should I use interface or type — and how do I switch?
- Default is interface. Use interface for plain object shapes you might extend later; use type for unions, intersections, mapped types, or tuple shapes. To switch, toggle 'just types' in the options pane — the generator emits `type Root = { ... }` instead of `interface Root { ... }`. Note that you can't extend a type with declaration-merging the way you can interface, so if you're shipping a public API surface, interface is usually the right pick.
- Why is my field typed as null instead of string | null?
- Because the sample only showed null for that field, and the generator has nothing else to go on. With a second sample containing a string value, quicktype would widen to `string | null`. Workarounds: paste a sample where the field has a real value (the resulting interface keeps `null` only if you add it), or change the literal `null` to `string | null` by hand.
- Can I generate Zod / Valibot schemas alongside the interfaces?
- Not from this UI. The output is type-only; runtime validators are out of scope to keep the generator deterministic. For Zod, generate interfaces here and write the schemas alongside (a 1:1 mapping you can refactor with a snippet), or use quicktype's CLI with `--lang TypeScriptZod` for a one-shot generation.
- How does the generator handle JSON keys that aren't valid TS identifiers?
- Keys with hyphens, leading digits, or special characters are emitted in quotes (`'2024-tax-year': number`) and accessed via index syntax (`r['2024-tax-year']`) at the call site. TypeScript is happy with quoted keys; the generated interface is structurally valid even when the JSON uses cursed key names.
- Why is the first generation slow?
- The quicktype engine (~700 KB compressed) is loaded into a Web Worker on first use. After that it stays cached for the rest of the session — subsequent generations are near-instant. The Worker means the engine never blocks the UI thread, so paste-and-tab-away keeps working at full speed.
- Does the output have correct line wrapping for prettier / eslint?
- It's pre-formatted at column ~100 with 4-space indent. Prettier with default settings will reformat to 2-space indent and column 80; that's a no-op semantic change. Drop the output through `prettier --write` if you want it to look like the rest of your codebase before committing.
Content reviewed by ShiangYu Huang