openapi
Generate OpenAPI 3.1 spec from a tRPC router with @trpc/openapi CLI or programmatic API. Generate typed REST client with @hey-api/openapi-ts and configureTRPCHeyApiClient(). Configure transformers (superjson, EJSON) for generated clients. Alpha status.
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 18010f99da7dd3e5… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
tRPC -- OpenAPI
Alpha:
@trpc/openapiis versioned as11.x.x-alpha. APIs may change without notice.
Setup
1. Install
pnpm add @trpc/openapi
For HeyAPI client generation:
pnpm add @hey-api/openapi-ts -D
2. Generate the OpenAPI spec
The generator statically analyses your router's TypeScript types. It never executes your code.
CLI:
pnpm exec trpc-openapi ./src/server/index.ts -e appRouter -o openapi.json --title "My API" --version 1.0.0
| Option | Default | Description |
|---|---|---|
-e, --export <name> | AppRouter | Name of the exported router |
-o, --output <file> | openapi.json | Output file path |
--title <text> | tRPC API | OpenAPI info.title |
--version <ver> | 0.0.0 | OpenAPI info.version |
Programmatic:
import { generateOpenAPIDocument } from '@trpc/openapi';
const doc = await generateOpenAPIDocument('./src/server/index.ts', {
exportName: 'appRouter',
title: 'My API',
version: '1.0.0',
});
3. Generate a HeyAPI client from the spec
// scripts/codegen.ts
import { rmSync, writeFileSync } from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createClient } from '@hey-api/openapi-ts';
import { generateOpenAPIDocument } from '@trpc/openapi';
import { createTRPCHeyApiTypeResolvers } from '@trpc/openapi/heyapi';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const routerPath = path.resolve(__dirname, '..', 'server', 'index.ts');
const outputDir = path.resolve(__dirname, '..', 'client', 'generated');
const specPath = path.resolve(__dirname, '..', '..', 'openapi.json');
async function main() {
const doc = await generateOpenAPIDocument(routerPath, {
exportName: 'appRouter',
title: 'Example API',
version: '1.0.0',
});
writeFileSync(specPath, JSON.stringify(doc, null, 2) + '\n');
rmSync(outputDir, { recursive: true, force: true });
await createClient({
input: specPath,
output: outputDir,
plugins: [
{
name: '@hey-api/typescript',
'~resolvers': createTRPCHeyApiTypeResolvers(),
},
{
name: '@hey-api/sdk',
operations: { strategy: 'single' },
},
],
});
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Run it:
pnpm tsx scripts/codegen.ts
4. Configure and use the generated client at runtime
import { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';
import { client } from './generated/client.gen';
import { Sdk } from './generated/sdk.gen';
configureTRPCHeyApiClient(client, {
baseUrl: 'http://localhost:3000',
});
const sdk = new Sdk({ client });
// Queries -> GET, Mutations -> POST
const result = await sdk.greeting({ query: { input: { name: 'World' } } });
const user = await sdk.user.create({ body: { name: 'Bob', age: 30 } });
Core Patterns
CLI quick spec generation
# Default export name "AppRouter", output "openapi.json"
pnpm exec trpc-openapi ./src/server/router.ts
# Custom export name and output
pnpm exec trpc-openapi ./src/server/router.ts -e appRouter -o api.json --title "My API" --version 1.0.0
HeyAPI codegen with type resolvers (transformer setup)
When the server uses a transformer, pass createTRPCHeyApiTypeResolvers() to the @hey-api/typescript plugin so generated types use Date instead of string for date-time fields and bigint for bigint fields:
import { createClient } from '@hey-api/openapi-ts';
import { createTRPCHeyApiTypeResolvers } from '@trpc/openapi/heyapi';
await createClient({
input: './openapi.json',
output: './generated',
plugins: [
{
name: '@hey-api/typescript',
'~resolvers': createTRPCHeyApiTypeResolvers(),
},
{
name: '@hey-api/sdk',
operations: { strategy: 'single' },
},
],
});
Runtime client with superjson transformer
When the tRPC server uses superjson, the client must be configured with the same transformer:
// src/shared/transformer.ts
import superjson from 'superjson';
export const transformer = superjson;
// src/server/trpc.ts
import { initTRPC } from '@trpc/server';
import { transformer } from '../shared/transformer';
const t = initTRPC.create({ transformer });
export const router = t.router;
export const publicProcedure = t.procedure;
// src/client/index.ts
import { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';
import superjson from 'superjson';
import { client } from './generated/client.gen';
import { Sdk } from './generated/sdk.gen';
configureTRPCHeyApiClient(client, {
baseUrl: 'http://localhost:3000',
transformer: superjson,
});
const sdk = new Sdk({ client });
const event = await sdk.getEvent({
query: { input: { id: 'evt_1', at: new Date('2025-06-15T10:00:00Z') } },
});
// event.data.result.data.at is a Date object
MongoDB EJSON transformer (cross-language)
For non-TypeScript clients, EJSON provides a language-agnostic serialization format:
import type { TRPCDataTransformer } from '@trpc/server';
import type { Document } from 'bson';
import { EJSON } from 'bson';
export const ejsonTransformer: TRPCDataTransformer = {
serialize: (value) => EJSON.serialize(value),
deserialize: (value) => EJSON.deserialize(value as Document),
};
import { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';
import { client } from './generated/client.gen';
import { ejsonTransformer } from './transformer';
configureTRPCHeyApiClient(client, {
baseUrl: 'http://localhost:3000',
transformer: ejsonTransformer,
});
Response shape
All tRPC HTTP responses follow the envelope format. Access data through result.data:
const listResult = await sdk.user.list();
const users = listResult.data?.result.data; // the actual return value
const createResult = await sdk.user.create({ body: { name: 'nick' } });
const user = createResult.data?.result.data;
// user.createdAt instanceof Date === true (when transformer is configured)
Descriptions in the spec
Zod .describe() calls and JSDoc comments on types, routers, and procedures become description fields in the generated OpenAPI spec. No annotations or decorators required.
Common Mistakes
Missing transformer config in HeyAPI client
When the tRPC server uses superjson or another transformer, the generated HeyAPI client must also be configured with the same transformer via configureTRPCHeyApiClient(client, { transformer }). Without this, Date, Map, Set, and other non-JSON types will be silently wrong at runtime -- they arrive as raw serialized objects instead of their native types.
Wrong:
configureTRPCHeyApiClient(client, {
baseUrl: 'http://localhost:3000',
// missing transformer -- Dates will be broken
});
Right:
configureTRPCHeyApiClient(client, {
baseUrl: 'http://localhost:3000',
transformer: superjson, // must match server's transformer
});
Expecting subscriptions in OpenAPI spec
Subscriptions are currently excluded from OpenAPI spec generation. The generator silently skips any procedure with type: 'subscription'. SSE subscription support is planned but not yet available.
Forgetting createTRPCHeyApiTypeResolvers when using a transformer
Without the type resolvers plugin, HeyAPI generates string types for date-time fields instead of Date. The createTRPCHeyApiTypeResolvers() function maps date/date-time format to Date and bigint format to bigint in the generated TypeScript SDK.
Using the wrong export name
The CLI defaults to --export AppRouter (the type). If your file exports the router value as appRouter, pass -e appRouter. If the export is not found, the error message lists all available exports from the file.
See Also
- server-setup -- Required. Define routers and procedures before generating the spec.
- superjson -- Transformer configuration for server and client. OpenAPI clients need matching transformer config.
- validators -- Zod
.describe()calls propagate into OpenAPIdescriptionfields. - Full working example:
examples/openapi-codegen/
Files
1- SKILL.md
8e42e069418.7 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from trpc/trpc8
Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea
Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.
Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast
Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The
Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre
Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara
Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.
Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin
Related backend skillsscan passed
This skill should be used when the user asks to "add MCP server", "integrate MCP", "configure MCP in plugin", "use .mcp.json", "set up Model Context Protocol", "connect external service", mentions "${CLAUDE_PLUGIN_ROOT} with MCP", or discusses MCP server types (SSE, stdio, HTTP, WebSocket). Provides
Report browser/API/CLI/job/worker/webhook bugs. (gstack)
Java coding standards for Spring Boot and Quarkus services: naming, immutability, Optional usage, streams, exceptions, generics, CDI, reactive patterns, and project layout. Automatically applies framework-specific conventions. Use when writing or reviewing Java in a Spring Boot or Quarkus service.
Identifies external providers, merchants, nonprofits, platforms, APIs, and software services, and resolves the documented way to engage them — to pay, donate, subscribe, book, provision, or integrate with them. MUST be used BEFORE web search, model memory, or any other directory/vendor-lookup skill
PostHog integration for Ruby on Rails applications
Guides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.