Code Generation
Generation turns Go procedure registrations into the TypeScript contract consumed by tRPC clients.
Recommended Production Flow
Section titled “Recommended Production Flow”Use static analysis before building the frontend:
//go:generate go tool trpcgo generate -o ../web/gen/trpc.ts --zod ../web/gen/zod.ts ./...go generate ./...Static analysis reads source packages with go/packages, so it can preserve source-only information that reflection cannot see.
go tool trpcgo generate [flags] [packages]If no package pattern is provided, trpcgo analyzes ..
| Flag | Description |
|---|---|
-o, -output | TypeScript output file. Defaults to stdout. |
-dir | Working directory for package resolution. Defaults to .. |
-w, -watch | Watch Go files and regenerate on write/create events. |
-zod | Zod schema output file. |
-zod-mini | Emit zod/mini functional syntax. |
-enums | Runtime enum value object output file. |
Examples:
go tool trpcgo generate -o web/gen/trpc.ts ./...go tool trpcgo generate -o web/gen/trpc.ts --zod web/gen/zod.ts ./...go tool trpcgo generate -o web/gen/trpc.ts --enums web/gen/enums.ts ./...go tool trpcgo generate -o web/gen/trpc.ts --zod web/gen/zod.ts -w ./...Runtime And Dev Generation
Section titled “Runtime And Dev Generation”The router can write generated files from registered procedure reflection types:
if err := router.GenerateTS("web/gen/trpc.ts"); err != nil { return err}
if err := router.GenerateZod("web/gen/zod.ts"); err != nil { return err}In normal development, prefer the integrated watcher:
router := trpcgo.NewRouter( trpcgo.WithDev(true), trpcgo.WithTypeOutput("../web/gen/trpc.ts"), trpcgo.WithZodOutput("../web/gen/zod.ts"), trpcgo.WithEnumsOutput("../web/gen/enums.ts"),)defer router.Close()When trpc.NewHandler is constructed, trpcgo starts the watcher. It generates once from source, then regenerates when .go files change. If source analysis fails because the Go code is temporarily broken, previous generated files are preserved.
The watcher runs static analysis, so it resolves const unions and the runtime enum value objects (WithEnumsOutput) at full fidelity — the same as the CLI. The reflection methods GenerateTS and GenerateZod cannot see Go const groups, so there is deliberately no GenerateEnums counterpart: runtime enum value objects come only from WithEnumsOutput under the watcher or --enums on the CLI.
Use WithWatchPackages to avoid watching unrelated frontend or generated directories in larger repositories:
trpcgo.WithWatchPackages("./cmd/api", "./internal/...")Static Analysis Vs Reflection
Section titled “Static Analysis Vs Reflection”| Feature | Static CLI | Runtime reflection |
|---|---|---|
| Registered procedure input/output types | Yes | Yes |
json, tstype, validate, ts_doc, zod_omit tags | Yes | Yes |
| Go doc comments as JSDoc | Yes | No |
| const groups as string/number unions | Yes | No |
runtime enum value objects (enums.ts) | Yes | No |
| aliases and defined basic types | Yes | Limited |
| source-level typed output parser discovery | Yes | Registered typed parsers only |
Const groups generate unions for reachable named types declared in the analyzed packages or other packages in the same Go module. Standard-library and third-party constants are ignored, so types like time.Duration still generate as their normal primitive TypeScript shape.
Use the CLI for generated files committed or built in CI. Use dev watch for a fast local feedback loop.
Generated TypeScript Shape
Section titled “Generated TypeScript Shape”Generated trpc.ts includes:
// Code generated by trpcgo. DO NOT EDIT.- Type-only imports from
@trpc/server. - Exported TypeScript definitions for reachable Go types.
$Query,$Mutation, and$Subscriptionhelper aliases only when needed.- Nested
AppRouterRecordgenerated from dot-separated procedure paths. - Exported
AppRouter. RouterInputsandRouterOutputshelpers when procedures exist.
Procedure paths become nested objects:
trpcgo.MustQuery(router, "user.get", getUser)trpcgo.MustMutation(router, "admin.user.ban", banUser)type AppRouterRecord = { user: { get: $Query<GetUserInput, User>; }; admin: { user: { ban: $Mutation<BanUserInput, BanUserResult>; }; };};Type Mapping
Section titled “Type Mapping”Common Go-to-TypeScript mappings:
| Go | TypeScript |
|---|---|
string | string |
bool | boolean |
| numeric types | number |
time.Time | string |
[]T, [N]T | T[] |
[]byte | string |
map[K]V | Record<K, V> |
any, interface{} | unknown |
json.RawMessage | unknown |
json.Number | number |
TrackedEvent[T] | T |
Pointer fields and fields tagged omitempty or omitzero become optional unless overridden with tstype:",required".
Detection Rules And Limits
Section titled “Detection Rules And Limits”Static generation discovers calls to the top-level registration functions such as Query, Mutation, Subscribe, SubscribeWithFinal, and their Void/Must variants.
Important limits:
- Procedure paths must be string literals for static analysis.
- Packages must load and type-check successfully.
- Custom wrapper functions are only detected if the analyzer can see the underlying top-level registration call with a literal path.
- Zod generation targets procedure input types and their dependencies, not output-only types.
- Reflection generation cannot emit source comments, const unions, or the runtime enum value objects derived from them. These need source analysis — the CLI or the dev watcher.
Zod Output
Section titled “Zod Output”Pass --zod or configure WithZodOutput to generate schemas for typed procedure inputs.
go tool trpcgo generate -o web/gen/trpc.ts --zod web/gen/zod.ts ./...If no procedures have typed inputs, runtime GenerateZod and dev watch remove stale Zod files. The CLI can still create an empty file.
See Zod Schemas for validate tag mapping, zod/mini, omitempty, dive, cross-field rules, and frontend usage.
Enum Values
Section titled “Enum Values”A TypeScript string-literal union is erased at runtime, so iterating an enum’s members, populating a <select>, or writing a membership guard means re-listing the values by hand — which silently drifts when the Go enum gains a member. Pass --enums or configure WithEnumsOutput to also emit the members as a runtime as const object:
go tool trpcgo generate -o web/gen/trpc.ts --enums web/gen/enums.ts ./...// enums.ts — Code generated by trpcgo. DO NOT EDIT.export const RoleEnum = { viewer: "viewer", admin: "admin", owner: "owner",} as const;The object is keyed by value, so Object.values(RoleEnum) iterates the members, Object.hasOwn(RoleEnum, value) tests membership, and RoleEnum.viewer autocompletes. Members come from the same analysis as the Role union, so they cannot drift from the type.
Enum objects are written to their own file to keep trpc.ts free of runtime values (type-only imports). They cover every named string enum, including output-only enums that never appear in a Zod input schema. Numeric enums are skipped.
If --enums or WithEnumsOutput is set but no named string enums are in scope, trpcgo still writes a generated header-only enums.ts. The CLI and dev watcher intentionally match here; unlike Zod output, the watcher does not delete an empty enum file.
Like const-union narrowing, enum objects come from source analysis, so they are produced by the CLI (--enums, including -w) and the dev watcher (WithEnumsOutput with WithDev) — not by the reflection-based GenerateTS/GenerateZod, which never see Go const groups.