The document
Generate an OpenAPI 3.1 document from the router's schemas, or from its types.
On this page
import { function generateOpenApi(router: object, opts: GenerateOpenApiOptions): OpenApiDocumentgenerateOpenApi } from '@trpc12/trpc/openapi';
import { function loadRouter<TRouter extends AnyRouter>(router: TRouter): Promise<TRouter>loadRouter } from '@trpc12/trpc/server';
const const doc: OpenApiDocumentdoc = function generateOpenApi(router: object, opts: GenerateOpenApiOptions): OpenApiDocumentgenerateOpenApi(await loadRouter<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}>(router: {
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}): Promise<...>
loadRouter(const appRouter: {
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}
appRouter), {
GenerateOpenApiOptions.info: OpenApiInfoinfo: { OpenApiInfo.title: stringtitle: 'API', OpenApiInfo.version: stringversion: '1.0.0' },
GenerateOpenApiOptions.servers?: readonly OpenApiServer[] | undefinedservers: [{ OpenApiServer.url: stringurl: 'https://api.example.com' }],
});The document has schemas for input, output and each declared error. filter({ path, procedure }) drops operations. target: 'rpc' describes the RPC endpoint instead of the REST routes. generateOpenApi throws on a lazy subtree that hasn't loaded, so pass the result of loadRouter().
From the types
generateOpenApi only sees runtime schemas. experimental_generateOpenApiFromTypes from @trpc12/trpc/openapi/typescript also reads the router's types with the TypeScript 7 compiler, so it documents:
- errors that are only inferred (returned with
error(), failed or yielded in an Effect), never declared, with their status: aTrpcError.Taggedclass's literalstatus, the built-in code's status, or400; - the input and output of procedures without static schemas (no
.output(), or a ctx-callback input).
// scripts/openapi.ts, run at build time
import { function experimental_generateOpenApiFromTypes(router: object, opts: GenerateOpenApiFromTypesOptions): Promise<OpenApiDocument>experimental_generateOpenApiFromTypes } from '@trpc12/trpc/openapi/typescript';
import { const appRouter: {
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}
appRouter } from '../src/router.ts';
const const doc: OpenApiDocumentdoc = await function experimental_generateOpenApiFromTypes(router: object, opts: GenerateOpenApiFromTypesOptions): Promise<OpenApiDocument>experimental_generateOpenApiFromTypes(const appRouter: {
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}
appRouter, {
GenerateOpenApiFromTypesOptions.file: string | URLfile: new var URL: new (url: string | URL, base?: string | URL) => URLURL('../src/router.ts', import.meta.ImportMeta.url: stringurl),
GenerateOpenApiFromTypesOptions.exportName?: string | undefinedexportName: 'appRouter', // the default
GenerateOpenApiOptions.info: OpenApiInfoinfo: { OpenApiInfo.title: stringtitle: 'API', OpenApiInfo.version: stringversion: '1.0.0' },
});
await module "node:fs/promises"fs.function writeFile(file: PathLike | fs.FileHandle, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: (ObjectEncodingOptions & {
mode?: Mode | undefined;
flag?: OpenMode | undefined;
flush?: boolean | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | BufferEncoding | null): Promise<void>
writeFile('openapi.json', var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)stringify(const doc: OpenApiDocumentdoc, null, 2));
// server
import import docdoc from './openapi.json' with { type: 'json' };
function openApiReference(opts: OpenApiReferenceOptions): HttpMiddlewareopenApiReference({ document: OpenApiDocument | (() => MaybePromise<OpenApiDocument>)document: import docdoc as OpenApiDocument });Where a procedure has a runtime schema, that schema wins, so the two generators agree. Types become JSON Schema the way JSON.stringify sends them:
Dateis adate-timestring, andtoJSON()is followed.- Functions, symbols,
bigintandundefinedare left out; optional properties aren'trequired. - Branded primitives (
string & { __brand }) are their primitive. - Recursive types become
components.schemas. - JSDoc comments become
descriptions.
typescript (7.x) is an optional peer dependency, needed only where this runs. tsconfig picks the project; it defaults to the nearest one that includes file.
An output declared with .output(schema, { validate: false }) isn't validated. The document then describes the schema's input side, which is what the resolver returns.