Automatically generate beautiful, interactive documentation for your tRPC APIs
Features • Installation • Quick Start • API Reference • Examples
Building great APIs is one thing documenting AND testing them shouldn't be hard. tRPC Docs Generator automatically creates stunning, fully interactive documentation from your tRPC router with zero configuration. Not only does it document your API, but it also provides a built-in API testing playground right in the browser!
- ⚡️ Zero Config - Works out of the box with any tRPC v11 router
- 🧪 Interactive Testing - Test endpoints directly in the browser with live fetch requests
- 🔍 Search & Filter - Instantly find endpoints with real-time search and smart filters
- 🎨 Beautiful UI - Modern, responsive design with smooth animations
- 🧠 Smart Schema Inference - Automatically extracts types from Zod validators
- 📝 Auto-Filled Examples - Pre-filled JSON with required fields, click to add optional fields
- 🔐 Header Management - Add custom headers (auth tokens, etc.), save and reuse them
- 📋 Ready-to-Copy Snippets - cURL, fetch, and tRPC client code generated for every endpoint
- ✅ Response Validation - Live responses checked against the documented output schema
- 🕘 Request History - Past requests saved per endpoint, replay any of them with one click
- 🌐 Deploy Anywhere - Works with Express, Next.js, Cloudflare Workers, and more
THE FEATURE - Test your API endpoints directly from the documentation:
- Live Fetch Requests - Send real HTTP requests to your API with a single click
- Pre-filled Request Bodies - JSON automatically populated with required fields from schemas
- Optional Fields Manager - Click badges to instantly add optional fields to your request
- Custom Headers - Add authentication tokens, content-types, and custom headers
- Header Persistence - Save common headers (like auth tokens) and reuse them across endpoints
- Real-time Responses - See formatted JSON responses with status codes and error details
- Auto-Method Detection - Uses GET for queries, POST for mutations automatically
- Configurable Endpoint - Point to localhost, staging, or production with one setting
- Debug Information - Shows full request details (URL, headers, body) on errors
Powered by Zod's toJSONSchema() method, the generator automatically:
- Extracts input/output schemas from your procedures
- Generates realistic JSON examples with proper types
- Creates TypeScript type definitions
- Identifies optional vs required fields
- Handles complex types (unions, intersections, arrays, enums, records)
- Date fields -
z.date()andz.coerce.date()are represented as ISO 8601 strings ({ type: "string", format: "date-time" }), matching how tRPC transports dates over the wire - Field Descriptions -
.describe()text on Zod schemas is preserved as an inline// commentin the generated TypeScript output
Every endpoint includes ready-to-copy code snippets in three formats, generated from the same example data used to pre-fill the test panel:
- cURL - shell command with proper query-string encoding for GET requests
- fetch - plain
fetch()snippet for browsers or Node - tRPC Client - typed
@trpc/clientcall usingcreateTRPCClient - Automatically respects the
superjsontransformer option, wrapping payloads in{ json: ... }when enabled - Also available programmatically via
generateSnippets(),generateCurlSnippet(),generateFetchSnippet(), andgenerateTrpcClientSnippet()
After sending a test request, the response is automatically checked against the route's documented output schema:
- ✅ Green confirmation when the response matches the schema
⚠️ A list of specific mismatches (missing fields, wrong types, disallowed values) when it doesn't- Powered by the standalone
validateAgainstSchema()export, usable outside the generated docs too
Every endpoint keeps a local history of its test requests:
- Automatically records the timestamp, input, and response status for each request you send
- Click any past entry to replay it - refills the request panel with that entry's input
- Scrollable item list with an always-visible "Clear history" button
- Stored in
localStorage, scoped per route
Quickly find what you need in large APIs:
- Real-time Search - Filter endpoints by name, path, description, or tags as you type
- Smart Filters - Filter by type (query/mutation), auth status (public/protected), or custom tags
- Filter Persistence - Your filter selections are saved across page reloads
- Visual Feedback - Active filters highlighted in blue, clear button turns red when active
- Dynamic Results - Shows count of visible endpoints and auto-hides empty sections
- Responsive Design - Perfect on desktop, tablet, and mobile
- Sidebar Navigation - Organized by route groups with smooth scrolling
- Expandable Cards - Click to reveal detailed schema information
- Type Badges - Visual indicators for query/mutation/subscription
- Auth & Role Badges - Clearly shows protected routes and permissions
- Deprecation Warnings - Highlights deprecated endpoints
- Dark-optimized Syntax - Code blocks with syntax highlighting
Routes are automatically organized by:
- Tags - Group related endpoints together
- Procedure Type - Separate queries, mutations, and subscriptions
- Statistics - See route counts at a glance
bun install trpc-docs-generator
# or
npm install trpc-docs-generatorRequirements:
@trpc/server^11.0.0- Zod v4+ (with
toJSONSchemasupport) - Node.js 16+ (with native ESM support)
Note: This package uses native ESM with explicit .js file extensions in imports, ensuring
compatibility with Node.js native module resolution (Node 16+, Node 22+).
import { collectRoutes, generateDocsHtml } from 'trpc-docs-generator';
import { appRouter } from './router';
// 1. Collect route information from your router
const routes = collectRoutes(appRouter);
// 2. Generate HTML documentation
const html = generateDocsHtml(routes, {
title: 'My API Documentation'
});
// 3. Serve it however you like
app.get('/docs', (req, res) => {
res.send(html);
});import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.meta<RouteMeta>().create();
export const appRouter = t.router({
// Query with metadata
getUser: t.procedure
.meta({
name: 'Get User',
docs: {
description: 'Retrieve user information by ID',
tags: ['Users'],
auth: true
}
})
.input(z.object({ userId: z.string() }))
.output(
z.object({
id: z.string(),
name: z.string(),
email: z.string().email()
})
)
.query(async ({ input }) => {
// Implementation
}),
// Mutation example
createPost: t.procedure
.meta({
name: 'Create Post',
docs: {
description: 'Create a new blog post',
tags: ['Posts'],
auth: true,
roles: ['admin', 'editor']
}
})
.input(
z.object({
title: z.string(),
content: z.string(),
published: z.boolean().optional()
})
)
.output(
z.object({
id: z.string(),
title: z.string(),
createdAt: z.string()
})
)
.mutation(async ({ input }) => {
// Implementation
})
});Every endpoint in the generated docs includes a fully functional testing panel:
- Open the docs - Navigate to your
/docsendpoint - Configure base URL - Click the gear icon (top-right) and enter your tRPC endpoint (e.g.,
http://127.0.0.1:8787/trpcsee Troubleshooting) - Click any endpoint - Expand the route card to see the testing panel
- Edit the request - Pre-filled JSON is ready, modify as needed or click badges to add optional fields
- Add headers (optional) - Add auth tokens or custom headers
- Click "Send Request" - See real-time response!
// Add custom headers for authenticated endpoints:
// 1. Click "Add Header" button
// 2. Enter: Authorization | Bearer your-token-here
// 3. Click "Save Headers" to persist for future requests
// 4. Use "Load Saved" on other endpoints to reuse headersThe testing panel shows:
- ✅ Success responses - Green badge with formatted JSON
- ❌ Error responses - Red badge with error details and full request debug info
- 🔍 Network errors - Clear error messages with troubleshooting tips
import express from 'express';
import { collectRoutes, generateDocsHtml } from 'trpc-docs-generator';
import { appRouter } from './router';
const app = express();
app.get('/docs', (req, res) => {
const routes = collectRoutes(appRouter);
const html = generateDocsHtml(routes, {
title: 'My API Docs'
});
res.send(html);
});
app.listen(3000);// app/docs/route.ts
import { collectRoutes, generateDocsHtml } from 'trpc-docs-generator';
import { appRouter } from '@/server/router';
export async function GET() {
const routes = collectRoutes(appRouter);
const html = generateDocsHtml(routes, {
title: 'API Documentation'
});
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
});
}
// Users accessing the docs will need to configure the base URL
// Example: http://localhost:3000/api/trpc (if your tRPC is at /api/trpc)import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { collectRoutes, generateDocsHtml } from 'trpc-docs-generator';
import { appRouter } from './router';
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
// Serve docs
if (url.pathname === '/docs') {
const routes = collectRoutes(appRouter);
const html = generateDocsHtml(routes, {
title: 'API Documentation'
});
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
});
}
// Handle tRPC requests
return fetchRequestHandler({
endpoint: '/trpc',
req: request,
router: appRouter
});
}
};"Failed to fetch" or CORS errors:
- Ensure the base URL hostname matches exactly how you're accessing the docs
- If docs are at
http://localhost:3000/docs, usehttp://localhost:3000/trpc - Don't mix
localhostand127.0.0.1- this causes origin mismatch
- If docs are at
- Verify your tRPC server has CORS configured to allow requests from the docs origin
- Check browser console for specific CORS error messages
404 Not Found:
- Verify the complete mount path is included in base URL
- If tRPC is at
/api/trpc, usehttp://localhost:3000/api/trpc, not justhttp://localhost:3000
- If tRPC is at
- Ensure your tRPC server is actually running
- Test the endpoint directly with curl or Postman to verify it works
Base URL not saving:
- Check browser console for localStorage errors
- Ensure you're using a modern browser with localStorage support
- Try clearing browser cache and reconfiguring
Invalid JSON errors:
- Ensure proper JSON syntax with double quotes for keys and string values
- Use the browser's JSON formatter or validator to check your input
- The testing panel validates JSON before sending
Collects route information from a tRPC router by traversing all procedures.
Parameters:
router- Your tRPC router instance
Returns:
- Array of
RouteInfoobjects containing:path- Route path (e.g.,'users.getById')type- Procedure type ('query'|'mutation'|'subscription')meta- Metadata object (if provided)inputSchema- JSON schema for input (if Zod validator exists)outputSchema- JSON schema for output (if Zod validator exists)inputExample- Auto-generated JSON example of inputoutputExample- Auto-generated JSON example of outputinputTypeScript- TypeScript type representation of inputoutputTypeScript- TypeScript type representation of outputinputOptionalFields- List of optional input fields with examplesoutputOptionalFields- List of optional output fields with examples
Generates a complete HTML documentation page from route information.
Parameters:
routes- Array of route information fromcollectRoutes()options- Optional configuration:title- Page title (default:'API Documentation')transformer- Data transformer used by tRPC router (optional)- Set to
'superjson'if your router uses the superjson transformer - This ensures the test playground correctly wraps requests/responses in
{json: ...}format - See SuperJSON Support for details
- Set to
Returns:
- Complete HTML string ready to serve
Generates ready-to-copy code snippets (cURL, fetch, and tRPC client) for a single route. Individual
generators are also exported: generateCurlSnippet(), generateFetchSnippet(), and
generateTrpcClientSnippet() (each returns a string).
Parameters:
route- A singleRouteInfoobject (e.g. fromcollectRoutes())options- Optional configuration:transformer- Set to'superjson'to wrap payloads in{ json: ... }baseUrlPlaceholder- Token used in place of the real base URL (default'{{BASE_URL}}')
Returns:
RouteSnippets-{ curl: string; fetch: string; trpcClient: string }
Validates arbitrary data against a JSON Schema object (as produced by collectRoutes()), returning
an array of human-readable mismatch descriptions. An empty array means data matches the schema.
Used internally to validate live responses in the test playground; also exported for standalone use.
Type definition for route metadata (use with initTRPC.meta<RouteMeta>()):
type RouteMeta = {
/** Human-readable name for the route */
name?: string;
/** Documentation metadata */
docs?: {
/** Description of what the route does */
description?: string;
/** Tags for grouping routes */
tags?: string[];
/** Whether this route is deprecated */
deprecated?: boolean;
/** Whether this route requires authentication */
auth?: boolean;
/** Roles allowed to access this route */
roles?: string[];
};
};If your tRPC router uses the superjson transformer, you
need to configure the docs generator to match. SuperJSON wraps requests/responses in a special
format ({json: ...}) to support JavaScript types that standard JSON doesn't handle (Date,
undefined, BigInt, Map, Set, etc.).
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { collectRoutes, generateDocsHtml } from 'trpc-docs-generator';
// Your tRPC router with superjson transformer
const t = initTRPC.create({
transformer: superjson // Using superjson
});
const appRouter = t.router({
getEvent: t.procedure.input(z.object({ id: z.string() })).query(() => ({
id: '123',
name: 'Conference',
startDate: new Date() // Date objects work with superjson!
}))
});
// Generate docs WITH superjson support
const routes = collectRoutes(appRouter);
const html = generateDocsHtml(routes, {
title: 'My API Documentation',
transformer: 'superjson' // Enable superjson in test playground
});Without transformer: 'superjson':
- Request:
{"id": "123"} - Your superjson-enabled tRPC server expects:
{"json": {"id": "123"}} - Result: ❌ Request fails with "Invalid input: expected object, received undefined"
With transformer: 'superjson':
- Request:
{"json": {"id": "123"}} - Response unwrapping: Automatically extracts data from
{result: {data: {json: {...}}}} - Result: ✅ Everything works perfectly
Enable transformer: 'superjson' if your tRPC router:
- Uses
superjsontransformer ininitTRPC.create({ transformer: superjson }) - Returns Date objects, undefined values, BigInt, Map, Set, or other non-JSON types
- Is configured with any custom transformer that wraps data
If you're using standard JSON (no transformer), you don't need this option.
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import type { RouteMeta } from 'trpc-docs-generator';
const t = initTRPC.meta<RouteMeta>().create();
export const appRouter = t.router({
// Public endpoint
health: t.procedure
.meta({
name: 'Health Check',
docs: {
description: 'Check if the API is running',
tags: ['System']
}
})
.output(z.object({ status: z.literal('ok') }))
.query(() => ({ status: 'ok' as const })),
// Protected endpoint
users: t.router({
list: t.procedure
.meta({
name: 'List Users',
docs: {
description: 'Get a paginated list of users',
tags: ['Users'],
auth: true,
roles: ['admin']
}
})
.input(
z.object({
page: z.number().min(1).default(1),
limit: z.number().min(1).max(100).default(10),
search: z.string().optional()
})
)
.output(
z.object({
users: z.array(
z.object({
id: z.string(),
name: z.string(),
email: z.string().email()
})
),
total: z.number(),
page: z.number(),
totalPages: z.number()
})
)
.query(async ({ input }) => {
// Implementation
}),
// Deprecated endpoint
getByEmail: t.procedure
.meta({
name: 'Get User by Email',
docs: {
description: 'Retrieve user by email address',
tags: ['Users'],
deprecated: true,
auth: true
}
})
.input(z.object({ email: z.string().email() }))
.output(
z.object({
id: z.string(),
name: z.string(),
email: z.string()
})
)
.query(async ({ input }) => {
// Implementation
})
})
});
export type AppRouter = typeof appRouter;The generator handles complex Zod schemas:
t.procedure.input(
z.object({
// Unions
status: z.union([z.literal('active'), z.literal('inactive')]),
// Enums
role: z.enum(['admin', 'user', 'guest']),
// Intersections
profile: z.object({ name: z.string() }).and(z.object({ age: z.number() })),
// Arrays
tags: z.array(z.string()),
// Nested objects
settings: z.object({
notifications: z.object({
email: z.boolean(),
push: z.boolean()
})
}),
// Records (key-value maps)
metadata: z.record(z.string(), z.string()).optional()
// Alternative: record with any string value
// metadata: z.record(z.string()).optional(),
})
);-
Route Collection -
collectRoutes()traverses your tRPC router's internal structure to extract all procedures and their metadata -
Schema Inference - Uses Zod's
toJSONSchema()method (Zod v4+) to convert Zod validators into JSON Schema format -
Example Generation - Automatically generates:
- Realistic JSON examples from schemas
- TypeScript type definitions
- Lists of optional fields
-
HTML Generation - Creates a beautiful, self-contained HTML page with:
- Embedded CSS (no external dependencies)
- Interactive JavaScript for expandable cards and navigation
- Responsive design
- Organized sections and groups
t.procedure.meta({
name: 'Create User', // Clear, readable name
docs: {
description: 'Creates a new user account with the provided information',
tags: ['Users', 'Authentication'], // Logical grouping
auth: true, // Show auth requirement
roles: ['admin'] // Show role requirements
}
});t.procedure.meta({
docs: {
deprecated: true,
description: 'Use users.getById instead'
}
});const appRouter = t.router({
// All auth-related routes
login: t.procedure.meta({ docs: { tags: ['Authentication'] } }),
logout: t.procedure.meta({ docs: { tags: ['Authentication'] } }),
register: t.procedure.meta({ docs: { tags: ['Authentication'] } }),
// All user-related routes
getUser: t.procedure.meta({ docs: { tags: ['Users'] } }),
updateUser: t.procedure.meta({ docs: { tags: ['Users'] } }),
deleteUser: t.procedure.meta({ docs: { tags: ['Users'] } })
});Fully typed with TypeScript! Import types for better developer experience:
import type { RouteMeta, RouteInfo, DocsGeneratorOptions } from 'trpc-docs-generator';To generate a standalone HTML preview for testing the documentation output:
bun scripts/generate-preview.tsThis creates preview.html in the root directory with example routes. Open it in your browser to
see the generated documentation without setting up a server.
The preview script includes a comprehensive example router with various features like authentication, roles, deprecated endpoints, and different schema types.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Lior Cohen




