typia.json.schemas โ emit JSON Schema / OpenAPI from a TypeScript type
If youโve ever hand-written a JSON Schema or an OpenAPI components block, you know how mechanical it is. typia generates it from the TypeScript type at compile time. Same model as the validators โ no extra schema file to maintain.
namespace json {
function schema<T, Version extends "3.0" | "3.1" = "3.1">(): IJsonSchemaUnit<Version, T>;
function schemas<Schemas extends unknown[], Version extends "3.0" | "3.1" = "3.1">(): IJsonSchemaCollection<Version, Schemas>;
function application<Class extends object, Version extends "3.0" | "3.1" = "3.1">(): IJsonSchemaApplication<Version, Class>;
}| Function | Use case |
|---|---|
json.schema<T>() | A single top-level schema, plus a components map for any $refs the type pulls in |
json.schemas<[T1, T2, ...]>() | Multiple top-level types sharing one components map (the common case for an OpenAPI components.schemas) |
json.application<Class>() | Class methods โ a list of {name, parameters, output} JSON Schemas โ handy for framework-neutral tooling that wants per-method schemas without typiaโs LLM-specific config |
For the LLM-specific shape (with $defs, additionalProperties: false, strict mode, โฆ), use typia.llm.parameters or typia.llm.application instead.
Version selects the JSON Schema dialect:
"3.1"(default) โ OpenAPI 3.1, which is JSON Schema 2020-12. Supports tuples, pattern properties,nullas a primitive."3.0"โ OpenAPI 3.0 / Swagger compatibility. No tuples, no pattern properties.
Stick with "3.1" unless a downstream consumer demands the older dialect.
First example
import typia, { tags } from "typia";
interface User {
id: string & tags.Format<"uuid">;
age: number & tags.Type<"uint32">;
email?: string & tags.Format<"email">;
}
const schemas = typia.json.schemas<[User]>();
// โ an IJsonSchemaCollection with `User` under components.schemasThe output is a plain object. Use it directly with typia and @typia/utils, hand it to ajv when your JSON Schema consumer accepts typiaโs emended shape, or save it to disk for tooling that wants schemas at rest.
typia.json.schema, typia.json.schemas, and typia.json.application return typiaโs emended schema representation. It is designed for typia and @typia/utils converters first, not for direct assignment to every third-party OpenAPI SchemaObject type. When a tool expects raw OpenAPI 3.1 schemas, convert the components with OpenApiConverter.downgradeComponents:
import typia from "typia";
import { OpenApiConverter } from "@typia/utils";
const collection = typia.json.schemas<[User]>();
const components = OpenApiConverter.downgradeComponents(
collection.components,
"3.1",
);
// Use components.schemas in an OpenAPI 3.1 document or plugin option.This matters for schema-level examples: typia keeps tags.Examples<T> as named examples in the emended schema, while OpenAPI 3.1โs Schema Object uses JSON Schemaโs array-shaped examples keyword. The converter drops the names and emits the raw OpenAPI form.
TypeScript Source
import typia, { tags } from "typia";
export const MemberSchema = typia.json.schemas<[IMember], "3.0">();
interface IMember {
/** Unique user ID generated by server. */
id: string & tags.Format<"uuid">;
/** Email address of the member. */
email: string & tags.Format<"email">;
/**
* Age of the member.
*
* For reference, only adult can be a member.
*/
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}undefined
export namespace json {
export function schemas<
Schemas extends unknown[],
Version extends "3.0" | "3.1" = "3.1",
>(): IJsonSchemaCollection<Version>;
}What gets included
Anything you express in the TypeScript type carries over:
tags.Format<"uuid">โ"format": "uuid"tags.MaxLength<100>โ"maxLength": 100tags.Minimum<0>โ"minimum": 0- JSDoc description on a property โ
"description": "..."
A few JSON-Schema-only comment tags are also recognized โ they donโt affect runtime validation but they show up in the emitted schema:
| Tag | Effect |
|---|---|
@title Something | Sets title |
@deprecated | Sets deprecated: true |
@hidden / @internal | Excludes the field from the emitted schema |
TypeScript Source
import typia, { tags } from "typia";
export const SpecialCommentTagSchema = typia.json.schemas<[Special], "3.1">();
interface Special {
/**
* Deprecated tags are just used for marking.
*
* @deprecated
* @title Unsigned integer
*/
type: number & tags.Type<"int32">;
/**
* Internal tagged property never be shown in JSON schema.
*
* It even doesn't be shown in other `typia` functions like `assert<T>()`.
*
* @internal
*/
internal: number[];
/**
* Hidden tagged property never be shown in JSON schema.
*
* However, it would be shown in other `typia` functions like
* `stringify<T>()`.
*
* @ignore
*/
hidden: boolean;
/**
* You can limit the range of number.
*
* @exclusiveMinimum 19
* @maximum 100
*/
number?: number;
/**
* You can limit the length of string.
*
* Also, multiple range conditions are also possible.
*/
string: string &
(
| (tags.MinLength<3> & tags.MaxLength<24>)
| (tags.MinLength<40> & tags.MaxLength<100>)
);
/**
* You can limit the pattern of string.
*
* @pattern ^[a-z]+$
*/
pattern: string;
/**
* You can limit the format of string.
*
* @format date-time
*/
format: string | null;
/** In the Array case, possible to restrict its elements. */
array: Array<string & tags.Format<"uuid">> & tags.MinItems<3>;
}Custom fields
OpenAPI-style schema consumers commonly use the x-* prefix for non-standard extensions. To embed x-* properties (for vendor-specific tooling, internal flags, anything that isnโt part of the standard), use tags.TagBase.schema or the tags.JsonSchemaPlugin helper:
TypeScript Source
import typia, { tags } from "typia";
export const SpecialTypeTagSchema = typia.json.schemas<[IAccount]>();
type Monetary<Value extends string> = tags.TagBase<{
target: "number";
kind: "monetary";
value: Value;
schema: {
"x-monetary": Value;
};
}>;
type Placeholder<Value extends string> = tags.JsonSchemaPlugin<{
"x-placeholder": Value;
}>;
interface IAccount {
code: string & Placeholder<"Write your account code please">;
balance: number & Monetary<"dollar">;
}For standard schema keywords that TypeScript cannot infer, such as OpenAPI 3.1 / JSON Schema patternProperties, use the same escape hatch when the annotation is only for schema consumers:
import typia, { tags } from "typia";
type XHeaders = Record<string, string> &
tags.JsonSchemaPlugin<{
patternProperties: {
"^x-": { type: "string" };
};
additionalProperties: false;
}>;
typia.json.schema<XHeaders>();JsonSchemaPlugin is schema-only metadata. It does not change validator behavior; if runtime validation must reject keys outside the pattern, encode that rule with a validating custom tags.TagBase or validate the keys separately.
Restrictions
bigint is rejected
JSON Schema has no bigint. typia refuses to compile a schema for a type that contains one:
TypeScript Source
import typia, { tags } from "typia";
interface Something {
bigint: bigint;
array: bigint[];
nested: Nested;
}
interface Nested {
uint64: bigint & tags.Type<"uint64">;
}
typia.json.schemas<[Something]>();Native classes โ only Date is allowed
Map, Set, Uint8Array, โฆ are rejected. Date is special-cased: it serializes as string & tags.Format<"date-time">, so typia accepts it.
TypeScript Source
import typia from "typia";
interface Native {
date: Date;
}
typia.json.schemas<[Native]>();Where to go next
- Reading JSON safely โ
json.*Parse - Writing JSON fast โ
json.*Stringify - LLM-compatible schema for function calling โ
llm.parameters - Constraint cheatsheet โ Special Tags