Open-source project
stephenh/ts-proto avatar
stephenh/ts-proto

ts-proto's published manifest declares ISC while the repository is Apache-2.0

An idiomatic protobuf generator for TypeScript

2,596 stars388 forksTypeScriptApache-2.0

At a glance

What is it?
A code generator that turns protobuf schemas into TypeScript interfaces plus four helper functions, with client implementations for four transports and an explicit refusal to be an RPC framework. Its 2.x migration notes come with an apology about the release tooling that shipped them, and two sections of its own readme now contradict each other.
Who is it for?
ts-proto fits a team with existing protobuf schemas that wants plain data-shaped TypeScript interfaces and would rather choose its own transport than adopt someone else's, and the explicit non-goals make that a good match. Three things to check before you adopt it commercially.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The package manifest and the repository disagree about the licence

The repository carries the permissive Apache licence and the readme says nothing about licensing at all. The manifest that gets published to the package registry says something different.

json
"description": "",
"keywords": [],
"author": "",
"license": "ISC",

Four fields in a row, three of them empty and the fourth naming a licence that is not the one the repository was released under. The description is the field every registry shows in its search results and on the package page, so this package arrives at a potential user as a blank card. The author field being empty means there is no name attached to it either, which for a tool that generates the data layer of someone else's service is an odd omission. None of this affects whether the generated code works, and all of it affects whether you can answer a procurement question about it, which is a real cost for the enterprise users the project is otherwise shaped for.

The 2.x migration ships with an apology about its own release tooling

The version two release moved the byte level serialisation that the encoding and decoding functions use from one library to another, and the readme documents it in more detail than most projects document a stable change.

ts
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";

The readme is careful about who is affected: if you only called the two high level functions this is largely not a breaking change, and if you reached for the old reader and writer classes directly you have to change your imports. There is an escape hatch for anyone blocked, which is to pin the older line. And then there is a paragraph headed as a disclaimer and an apology, in which the author explains that the major version was intended to go out as an alpha and did not, because the release configuration was not set up correctly, so the change shipped as a major without a proper prerelease cycle.

What makes that paragraph more than an apology is that the cause is visible in the repository: a release configuration file at the root and seven separate release tooling packages in the development dependencies, covering changelog generation, commit analysis, publishing, git operations, the forge and release notes. The thing that went wrong is a checked-in configuration, which means the fix is too.

The goals section still credits the library that 2.x removed

Here is the contradiction, and it is in one document.

text
(Technically the `protobufjs/minimal` package is used for actually reading/writing bytes.)

That parenthetical sits inside the goals section, describing how the generator reaches the wire format, and it names the minimal build of the library the 2.x release notes say was replaced. So a reader who starts at the top of the readme, reads the goals, and concludes that the old library still does the byte handling is right about the architecture and wrong about the version, and a reader who starts with the migration notes is right about the version and never sees the stale claim. The likely explanation is that the parenthetical predates the migration and was missed, which is exactly the failure mode a single sentence in a parentheses is designed to produce. Nothing else in the visible goals contradicts the release notes; this one line does.

The output flag is derived from the plugin binary's file name

The quick start is two commands, and the second one has a gotcha worth understanding rather than memorising.

bash
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. ./simple.proto

The readme explains the naming convention: the output parameter is built from the suffix of the plugin's name, so the underscore separated suffix in the plugin path becomes the underscore separated output flag, following the compiler's own command line rules. That is a good convention and it has a consequence. If the binary is renamed, or if you install it under a different name, the flag you must pass changes with it and the failure is a complaint from the compiler about an unknown parameter rather than anything that points at the cause. The readme also gives a Windows variant that invokes the command shim with escaped backslashes, and notes that very old compiler versions do not understand the option flag at all.

Two invocation routes with two different option syntaxes

The same generator is driven two ways and the options are spelled differently in each. Through the compiler directly, options are comma separated flags on the command line. Through the schema registry tooling, you set a strategy option in the generation configuration so that every schema in the workspace is processed, and you exclude the dependency directory from the build configuration so the publish command does not try to read generated schemas as if they were sources. A third route uses a plugin the author published to that registry, where the options are written as a list of separate strings rather than one comma separated value.

yaml
build:
  excludes: [node_modules]

Two more options exist for module systems rather than for codegen: one to match a TypeScript configuration flag, and one to add a file extension to the generated imports so the output runs in a module environment. Neither is default, so both are things you find out about when your build fails rather than things you read in advance.

It is explicitly not an RPC framework, and it names two it did not build

The non-goals section is the most useful paragraph in the readme, because it tells you what you will still have to do.

text
it's more of a swiss-army knife ... that lets you build exactly the RPC framework you'd like on top of it

The reasoning given is that the remote procedure call side of the protobuf ecosystem is fragmented, so the project generates types and helpers and leaves the transport to you. Two frameworks built on top are linked, with an invitation to have yours linked too, and then a second non-goal: clients for the transcoding-based cloud endpoint style are not supported, with a tracking issue for anyone who wants to build one. That is an unusually complete statement of scope for a code generator, and it means the four client implementations it does ship, for two web transports, the Node transport and one application framework, are conveniences rather than a framework.

Fixtures are regenerated inside a container pinned to one architecture

The development scripts describe how the project builds itself, and one line constrains who can.

json
"proto2ts": "docker compose run --rm protoc update-code.sh",

Regenerating the generated fixtures and the comparison fixtures both run inside a compose service, and that service declares a fixed platform of one architecture and takes an architecture argument for the image build. So on a machine of the other architecture, contributing a codegen change means running an emulated container. Two Dockerfiles cover the two roles, one for the compiler and one for the project itself, and the generator is exposed as a small shim script rather than through the compiled entry point.

The rest of the toolchain is tidy and worth noting as a template: a type check script that validates four separate configuration files at once, a formatter scoped to two source directories, tests run through one runner with its own configuration file, a committed engine version file, and a package manager directory committed alongside its lock file so installs are reproducible. The readme itself is named with an unusual extension rather than the conventional one.

Editorial conclusion

ts-proto fits a team with existing protobuf schemas that wants plain data-shaped TypeScript interfaces and would rather choose its own transport than adopt someone else's, and the explicit non-goals make that a good match. Three things to check before you adopt it commercially. The published manifest declares a different licence from the repository and has an empty description and author, so resolve the licence from the repository file rather than the registry page. The 2.x release shipped as a major without the intended prerelease cycle because a release configuration was wrong, which is a useful signal about how release changes are reviewed here. And the goals section still credits the byte library that 2.x replaced, so trust the migration notes over the architecture description.

Frequently asked questions

how to use ts proto

Install the package, then invoke the protocol compiler with the bundled plugin and the output flag the plugin's file name implies. The readme explains the naming convention, so the flag follows from the plugin suffix rather than being an arbitrary string.

What does ts-proto generate from a schema?

A TypeScript interface for the message plus four accompanying functions to encode it, decode it, convert it to JSON and read it back from JSON. If the schema declares a service it also generates a typed service interface, and client implementations are available for four transports.

What changed in ts-proto 2.0?

The low level serialisation used by the encode and decode functions moved from one library to another. If you only used those two functions the change is largely not breaking, but anyone using the old reader and writer classes directly has to switch their imports, and anyone blocked can pin the 1.x line.

Is ts-proto a gRPC framework?

No, and the readme says so explicitly: it is a code generator and a set of helpers, on the grounds that the remote procedure call side of the protobuf ecosystem is fragmented. It links two frameworks built on top of it and invites links to others.

How do I use ts-proto with the Buf tooling?

Set the strategy option in the generation configuration so all schemas are processed, exclude the dependency directory from the build configuration so the publish command ignores generated files, and note that options there are written as a list of strings rather than comma separated flags.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. stephenh/ts-proto on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stephenh-ts-proto.svg)](https://hysenlabs.com/projects/stephenh-ts-proto)