Skip to content

Code Generation

Generate TypeScript types from your Go procedures so tRPC clients know which calls are available and what data they accept and return.

Use static analysis before building the frontend:

//go:generate go tool trpcgo generate -o ../web/gen/trpc.ts --zod ../web/gen/zod.ts ./...
Terminal window
go generate ./...

Static analysis reads Go source without starting your server. It includes comments, aliases, and const values that runtime reflection cannot recover.

Terminal window
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:

Terminal window
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 ./...

After registering your procedures, you can generate files directly from the router using runtime reflection:

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()

With WithDev(true) and WithTypeOutput set, constructing trpc.NewHandler starts the watcher. It generates once from source, then regenerates on .go file create/write events. If source analysis fails because the Go code is temporarily broken, previous generated files are preserved. router.Close() stops the watcher.

The watcher uses the same static analysis as the CLI, including const unions and runtime enum objects. GenerateTS and GenerateZod use reflection, which cannot read Go const declarations. To generate enum objects, use WithEnumsOutput with the watcher or --enums with the CLI.

By default, the dev watcher analyzes .. If registrations live in other packages, include them with WithWatchPackages. This also limits which directories are watched:

trpcgo.WithWatchPackages("./cmd/api", "./internal/...")
Feature CLI / dev watcher 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
generic struct declarations Generic TypeScript interfaces Concrete interfaces per instantiation
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 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 $Subscription helper aliases only when needed.
  • Nested AppRouterRecord generated from dot-separated procedure paths.
  • Exported AppRouter.
  • RouterInputs and RouterOutputs helpers 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>;
};
};
};

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] subscription item { id: string; data: T }

For subscriptions, TrackedEvent[T] and *TrackedEvent[T] generate the { id, data } value that httpSubscriptionLink passes to onData. In query results, inputs, or nested fields, TrackedEvent[T] keeps its Go JSON fields: ID, Retry, and Data.

Pointer fields and fields tagged omitempty or omitzero become optional. On named structs, validate:"required" or tstype:",required" makes them required. See Struct Tags for optionality and JSON null handling.

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.
  • Only packages matched by the supplied patterns are scanned for registrations. Include subpackages with ./... when needed.
  • 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.

Pass --zod or configure WithZodOutput to generate schemas for typed procedure inputs.

Terminal window
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 writes an empty file at the requested Zod path.

See Zod Schemas for validate tag mapping, zod/mini, omitempty, dive, cross-field rules, and frontend usage.

A TypeScript union gives you type checking, but its values are unavailable at runtime. To populate a <select> or check enum membership, pass --enums or configure WithEnumsOutput. This generates an as const object from the same Go constants:

Terminal window
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) lists the members, Object.hasOwn(RoleEnum, value) tests membership, and RoleEnum.viewer autocompletes. Regenerating updates both the object and the Role union.

Enum objects are written to their own file so you can import trpc.ts using type-only imports. They cover named string enums reachable from procedure inputs or outputs, including enums that never appear in a Zod input schema. Numeric enums are skipped.

If no named string enums are reachable, the CLI and dev watcher write an enums.ts containing only the generated-file header.