Protocol Buffer Compiler for TypeScript
protoc-gen-ts is a `protoc` plugin that generates plain TypeScript source files from Protocol Buffer `.proto` definitions, effectively replacing separate `.d.ts` declaration files. It is actively maintained, with the current stable version being `0.8.7`, and new features/fixes are delivered through frequent minor releases. A key differentiator is its direct TypeScript output which eliminates common prefixes (e.g., `getField`) and exposes fields as standard getters/setters, along with `fromObject` and `toObject` methods for robust bidirectional mapping between JSON and message instances, supporting deep structures without runtime type reflection. It offers native support for gRPC Node (`@grpc/grpc-js`) and gRPC Web, including options for promise-based RPC calls. Messages defined within a `package` directive in the `.proto` file are by default encapsulated within a TypeScript namespace, though this behavior can be toggled.
Common errors
-
protoc-gen-ts: program not found
cause The `protoc` compiler cannot locate the `protoc-gen-ts` executable in the system's PATH.fixInstall `protoc-gen-ts` globally (`npm install -g protoc-gen-ts`) and ensure your PATH includes npm's global bin directory. Alternatively, specify the full path to the plugin: `protoc --plugin=protoc-gen-ts=$(which protoc-gen-ts) -I=...`. -
TS2307: Cannot find module './myproto_pb' or its corresponding type declarations.
cause The TypeScript compiler cannot resolve the import path to the generated Protocol Buffer files, either due to incorrect path, missing `tsconfig.json` configuration, or the generated files not being in the include path.fixVerify that the `--ts_out` option in your `protoc` command points to the correct output directory. Ensure your `tsconfig.json` includes this directory in `include` or `files`, and that `baseUrl` and `paths` are configured correctly if you are using path aliases. Adjust import statements to match the actual generated file names and paths. -
TypeError: Cannot read properties of undefined (reading 'name') at Object.fromObject
cause This typically occurs when `fromObject` is called with an invalid or unexpected JSON structure, such as a missing nested object or an array where a single object is expected, and an attempt is made to access properties on `undefined`.fixEnsure the JSON object passed to `fromObject` strictly adheres to the structure of your `.proto` message definition, especially for nested messages and repeated fields. Verify all required fields are present and correctly typed. -
TS2339: Property 'myField' does not exist on type 'MyMessage'.
cause This can happen if you are trying to access a field using a name that does not match the generated TypeScript property, or if the field is part of a `oneof` and is not currently set, or if an older version of TypeScript is used that doesn't fully support certain generated constructs.fixCheck your `.proto` file for the exact field name. By default, fields are named as-is. If `json_names` option is enabled, check for camelCase. For `oneof` fields, access the field through the `oneof_name` property first. Ensure your TypeScript version is compatible (e.g., `ts-5` is allowed as a peer dep since `0.8.7`).
Warnings
- breaking In version `0.8.5`, `getters` and `toObject` methods were changed to return the *default value* for a field if it is not present, instead of `undefined`. This affects behavior for optional fields, proto2 fields, and oneof fields that are not set, potentially requiring code adjustments if `undefined` or `null` checks were previously used.
- deprecated The `index.bzl` file has been deprecated in version `0.8.7`. Users integrating with Bazel build systems may need to update their Bazel configurations to align with the new recommended practices for Bazel integration.
- gotcha By default, `protoc-gen-ts` generates TypeScript namespaces corresponding to the `package` directive in your `.proto` files. If you prefer a flatter module structure or encounter issues with namespace resolution, this behavior can be altered.
- gotcha `protoc-gen-ts` is a `protoc` plugin and must be discoverable by the `protoc` executable. If `protoc` cannot find the plugin, it will fail with an error indicating the program is not found.
- gotcha The package `protoc-gen-ts` itself does not have runtime npm dependencies beyond standard Node.js/TypeScript. However, the *generated code* often relies on runtime libraries for gRPC functionality (e.g., `@grpc/grpc-js` or `grpc`). These must be installed separately in your project if you generate gRPC service clients/servers.
Install
-
npm install protoc-gen-ts -
yarn add protoc-gen-ts -
pnpm add protoc-gen-ts
Imports
- Change
const { Change } = require('./myproto_pb');import { Change } from './myproto_pb'; - Kind
import Kind from './myproto_pb';
import { Kind } from './myproto_pb'; - MyService
import { MyService } from './myproto_pb';import { MyServiceClient } from './myproto_pb';
Quickstart
import { Change, Kind, Author } from './myproto_pb'; // Assumes protoc-gen-ts generated './myproto_pb.ts'
// Construct a message instance
const author = new Author({
name: 'mary poppins',
role: 'maintainer'
});
const change = new Change({
kind: Kind.UPDATED,
patch: '@@ -7,11 +7,15 @@',
tags: ['no prefix', 'as is'],
name: 'patch for typescript 4.5',
author: author
});
// Serialize to bytes (Uint8Array)
const bytes: Uint8Array = change.serialize();
console.log('Serialized bytes:', bytes);
// Deserialize from bytes back into a message instance
const receivedChange: Change = Change.deserialize(bytes);
console.log('Deserialized Change object:', receivedChange);
console.log('Kind:', receivedChange.kind === Kind.UPDATED); // true
console.log('Author name:', receivedChange.author?.name); // mary poppins
// Using fromObject for easier JSON-to-message mapping
const jsonChange = Change.fromObject({
kind: Kind.DELETED,
patch: 'deleted line',
tags: ['cleanup'],
id: '12345',
author: {
name: 'john doe',
role: 'contributor'
}
});
console.log('Change from JSON:', jsonChange.toObject());
console.log('Author from JSON is an Author instance:', jsonChange.author instanceof Author);