Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

Hidden page. It waits for review: the sidebar, search and search engines leave it out, and listed pages don't link to it.

A contract from OpenAPI

Generate a typed contract module from any OpenAPI 3.0 or 3.1 document.

On this page

experimental_generateContractFromOpenApi(document, { prefix?, exportName?, patterns? }) turns an OpenAPI 3.0 or 3.1 document into the source of a @trpc12/trpc/contract module. Schemas are Effect Schemas, translated by Effect's SchemaRepresentation; each operation becomes a procedure with its route meta (under .concat(experimental_openapi())), so the contract works with experimental_openApiLink and experimental_validationLink against any REST API:

import { function experimental_generateContractFromOpenApi(document: OpenApiSource, opts?: GenerateContractOptions): stringexperimental_generateContractFromOpenApi } from '@trpc12/trpc/openapi';

const const document: anydocument = await (
  await function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)fetch('https://petstore.example/openapi.json')
).Body.json(): Promise<any>json();
await 
function writeFile(file: PathLike | 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
(
'src/petstore.ts', function experimental_generateContractFromOpenApi(document: OpenApiSource, opts?: GenerateContractOptions): stringexperimental_generateContractFromOpenApi(const document: anydocument, { GenerateContractOptions.exportName?: string | undefinedexportName: 'petstore', }), );
// src/petstore.ts (generated)
export const 
const Pet: Schema.Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>
Pet
= import SchemaSchema.
function Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>(fields: {
    readonly id: Schema.Number;
    readonly name: Schema.String;
}): Schema.Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>
Struct
({
id: Schema.Numberid: import SchemaSchema.const Number: Schema.NumberNumber.BottomWithoutNew<number, number, never, never, Number, Number, number, number, readonly [], number, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<number>, ...checks: Check<number>[]): Schema.Numbercheck(import SchemaSchema.function isInt(annotations?: Schema.Annotations.Filter): Filter<number>isInt()), name: Schema.Stringname: import SchemaSchema.const String: Schema.StringString, }); const
const c: ContractBuilder<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>>
c
=
const trpcContract: {
    create<TConfig extends ContractConfig = {}>(): ContractBuilder<ContractRootDef<TConfig["meta"] extends object ? TConfig["meta"] : {}>>;
}
trpcContract
.create<{}>(): ContractBuilder<ContractRootDef<{}>>create().
ContractBuilder<ContractRootDef<{}>>.concat<RootDef<{}, OpenApiMeta>>(extension: ProcedureBuilder<RootDef<{}, OpenApiMeta>>): ContractBuilder<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>>
concat
(function experimental_openapi(): ProcedureBuilder<RootDef<{}, OpenApiMeta>>experimental_openapi());
export const
const petstore: {
    pets: {
        list: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                readonly limit?: number | undefined;
            };
            inputOut: {
                readonly limit?: number | undefined;
            };
            errors: SchemaValidationError<Schema.Struct<{
                readonly limit: Schema.optionalKey<Schema.Number>;
            }>>;
            requires: never;
        }>, {
            outputIn: readonly {
                readonly id: number;
                readonly name: string;
            }[];
            outputOut: readonly {
                readonly id: number;
                readonly name: string;
            }[];
            requires: never;
        }>>;
    };
}
petstore
= {
pets: {
    list: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
        meta: {
            route?: Route | undefined;
        };
    }>, {
        inputIn: {
            readonly limit?: number | undefined;
        };
        inputOut: {
            readonly limit?: number | undefined;
        };
        errors: SchemaValidationError<Schema.Struct<{
            readonly limit: Schema.optionalKey<Schema.Number>;
        }>>;
        requires: never;
    }>, {
        outputIn: readonly {
            readonly id: number;
            readonly name: string;
        }[];
        outputOut: readonly {
            readonly id: number;
            readonly name: string;
        }[];
        requires: never;
    }>>;
}
pets
: {
list: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        readonly limit?: number | undefined;
    };
    inputOut: {
        readonly limit?: number | undefined;
    };
    errors: SchemaValidationError<Schema.Struct<{
        readonly limit: Schema.optionalKey<Schema.Number>;
    }>>;
    requires: never;
}>, {
    outputIn: readonly {
        readonly id: number;
        readonly name: string;
    }[];
    outputOut: readonly {
        readonly id: number;
        readonly name: string;
    }[];
    requires: never;
}>>
list
:
const c: ContractBuilder<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>>
c
.
ContractBuilder<ContractWith<ContractRootDef<{}>, { meta: { route?: Route | undefined; }; }>>.input<Schema.Struct<{
    readonly limit: Schema.optionalKey<Schema.Number>;
}>>(schema: Schema.Struct<{
    readonly limit: Schema.optionalKey<Schema.Number>;
}>): ContractBuilder<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        readonly limit?: number | undefined;
    };
    inputOut: {
        readonly limit?: number | undefined;
    };
    errors: SchemaValidationError<Schema.Struct<{
        readonly limit: Schema.optionalKey<Schema.Number>;
    }>>;
    requires: never;
}>>
input
(
const PetsListInput: Schema.Struct<{
    readonly limit: Schema.optionalKey<Schema.Number>;
}>
PetsListInput
)
.
ContractBuilder<ContractWith<ContractWith<ContractRootDef<{}>, { meta: { route?: Route | undefined; }; }>, { inputIn: { readonly limit?: number | undefined; }; inputOut: { ...; }; errors: SchemaValidationError<...>; requires: never; }>>.output<Schema.$Array<Schema.Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>>>(schema: Schema.$Array<Schema.Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>>): ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        readonly limit?: number | undefined;
    };
    inputOut: {
        readonly limit?: number | undefined;
    };
    errors: SchemaValidationError<Schema.Struct<{
        readonly limit: Schema.optionalKey<Schema.Number>;
    }>>;
    requires: never;
}>, {
    ...;
}>>
output
(
const PetsListOutput: Schema.$Array<Schema.Struct<{
    readonly id: Schema.Number;
    readonly name: Schema.String;
}>>
PetsListOutput
)
.
ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, { meta: { route?: Route | undefined; }; }>, { inputIn: { readonly limit?: number | undefined; }; inputOut: { ...; }; errors: SchemaValidationError<...>; requires: never; }>, { ...; }>>.meta<{
    readonly route: {
        readonly method: "GET";
        readonly path: "/pets";
    };
}>(meta: {
    readonly route: {
        readonly method: "GET";
        readonly path: "/pets";
    };
} & UndeclaredMeta<{
    route?: Route | undefined;
}, {
    readonly route: {
        readonly method: "GET";
        readonly path: "/pets";
    };
}>): ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        readonly limit?: number | undefined;
    };
    inputOut: {
        readonly limit?: number | undefined;
    };
    errors: SchemaValidationError<Schema.Struct<{
        readonly limit: Schema.optionalKey<Schema.Number>;
    }>>;
    requires: never;
}>, {
    ...;
}>>
meta
({
route: {
    readonly method: "GET";
    readonly path: "/pets";
}
route
: { method: "GET"method: 'GET', path: "/pets"path: '/pets' } })
.
ContractBuilder<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, { meta: { route?: Route | undefined; }; }>, { inputIn: { readonly limit?: number | undefined; }; inputOut: { ...; }; errors: SchemaValidationError<...>; requires: never; }>, { ...; }>>.query(): ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        readonly limit?: number | undefined;
    };
    inputOut: {
        readonly limit?: number | undefined;
    };
    errors: SchemaValidationError<Schema.Struct<{
        readonly limit: Schema.optionalKey<Schema.Number>;
    }>>;
    requires: never;
}>, {
    outputIn: readonly {
        readonly id: number;
        readonly name: string;
    }[];
    outputOut: readonly {
        readonly id: number;
        readonly name: string;
    }[];
    requires: never;
}>>
query
(),
}, };
  • Procedure paths come from operationIds, with dots nesting (pets.list); without one, from the method and path (deletePetsPetId).
  • GET is a query, a text/event-stream response a subscription, anything else a mutation. The first 2xx status becomes successStatus.
  • Path and query parameters merge with the JSON body into one input, like inputStructure: 'compact'.
  • Objects that don't mention additionalProperties become closed structs (extra keys are still ignored when decoding), except where allOf merges them.
  • Error responses in @trpc12/trpc's own shape (code and data) become .errors(), so a document from experimental_generateOpenApi round-trips with typed errors. Other APIs' errors stay unexpected errors.
  • patterns are ignored unless patterns: 'apply'; only use that with documents you trust.

Credits

Generating contracts from OpenAPI documents follows oRPC, which does it with a Hey API plugin.