Model or dataset
bufbuild/protobuf-es avatar
bufbuild/protobuf-es

protobuf-es drops getters and ships pure TypeScript, and the schema becomes the argument

Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.

1,676 stars214 forksTypeScriptApache-2.0

At a glance

What is it?
@bufbuild/protobuf generates plain TypeScript message types with no getters or setters, so every operation takes the schema as its first argument. The cost of that ergonomics is that the field shape lives in generated code rather than on a prototype, which changes how you refactor.
Who is it for?
Adopt protobuf-es when your JavaScript or TypeScript service needs Protobuf with real static types and you are willing to regenerate rather than hand-maintain message classes. Do not adopt it if your existing code depends on instance methods or on a `google-protobuf`-style API surface that other libraries in your dependency tree still expect.
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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Every function takes the schema, because nothing lives on the object

The one design decision that explains the whole library is that messages are plain objects. There are no getters, no setters, and nothing hung off a prototype. That means the field list cannot be discovered by inspecting an instance, so the schema has to travel with every call.

typescript
import { create, fromBinary, toBinary, toJson } from "@bufbuild/protobuf";
import { type User, UserSchema } from "./gen/user/v1/user_pb";

const user: User = create(UserSchema, {
  id: "123",
  firstName: "Alice",
});
user.lastName = "Smith";

const wire = toBinary(UserSchema, user);
user = fromBinary(UserSchema, wire);

Note `user.lastName = "Smith"` is an ordinary property assignment, and the README's own example carries the annotation `const user: User` because the initializer does not infer the full type. The JSON view comes from the same shape:

typescript
console.log(toJson(UserSchema, user))
// {"id": "123", "firstName": "Alice", "lastName": "Smith"}

The practical payoff is that messages serialize predictably, which matters when you `JSON.stringify` one in a log line or store one in a cache. The practical cost is that a message alone is not self-describing; you cannot call a runtime method on it to learn what it holds.

Conformance is the claim, and the runner is public

The project description calls this the only JavaScript Protobuf library fully compliant with Protobuf conformance tests, and the features list repeats it as 100% conformant against the official Protobuf test suite. That claim is checkable rather than rhetorical: the README links a public conformance runner and comparison table at bufbuild/protobuf-conformance.

The repository backs that up with a workspace dedicated to it. The root `package.json` lists `packages/protobuf-conformance` alongside `deno/conformance` and `bun/conformance`, so the same conformance suite runs against three runtimes. There is a `packages/upstream-protobuf` workspace too, which is what makes a side-by-side comparison against Google's implementation possible in the first place.

Bundle size is treated as a first-class property rather than an afterthought, with a dedicated `packages/bundle-size` workspace and a README in it holding side-by-side numbers against Google's generator. The top-level script ties the whole check into one command, running turbo across `build generate test lint attw bundle-size format license-header bootstrap`. The `attw` step comes from `@arethetypeswrong/cli`, so the published type surface is verified, not assumed.

This is the part that distinguishes the project from a wrapper. The conformance suite is the specification, and the comparison table lets you check the claim rather than trust the badge.

The quickstart, end to end

Start with the schema. The README's example defines a service and three messages in a proto3 file:

proto
syntax = "proto3";

package user.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

message GetUserRequest {
  string id = 1;
}

Generation is configured in `buf.gen.yaml`, and this is the file worth understanding before anything else:

yaml
version: v2
inputs:
  - directory: proto
plugins:
  - local: protoc-gen-es
    out: src/gen
    opt: target=ts

`local: protoc-gen-es` means the generator is a local binary rather than a remote plugin, and `opt: target=ts` selects TypeScript output. Change that to a JavaScript target and you get plain JS from the same schema.

Then install and generate:

sh
npm install @bufbuild/protobuf @connectrpc/connect
npm install --save-dev @bufbuild/protoc-gen-es @bufbuild/buf
npx buf generate

The README's conclusion is understated and correct: that is all it takes, and the typed messages plus Connect stubs now live in `src/gen`.

One thing the README does not spell out in this walkthrough is that `protoc` works as well as the Buf CLI, since the features list describes standard plugin-based generation working with both. If your pipeline already shells out to `protoc`, the same plugin binary is the only thing you need to swap.

Where protobuf-es sits against the older JS libraries

The README's comparison section is two sentences and both are about the alternatives rather than about this library.

`google-protobuf` is described as using a dated getter/setter API and as requiring third-party plugins for TypeScript. The second half is the sharper criticism: you generate JavaScript and then bolt types on from somewhere else, which means the type declarations are not produced by the same toolchain that produced the message classes.

`protobuf.js` is called a complicated library with three runtimes and three codegen targets. The README adds that it requires extra configuration to be completely conformant and is not type-safe under some configurations. That qualifier does the work: the issue is not that protobuf.js is wrong, but that its conformance and its type safety depend on how you configure it, so you have to verify both rather than inherit them.

The structural difference on this side is that generation is pure TypeScript from the start, so the emitted files are the source of truth for the types. The trade-off you accept is the schema-as-first-argument style described earlier, which is unusual enough that it will not drop into code written against either of these libraries.

Writing your own generator with protoplugin

Beyond message generation, the project ships a framework for code generation itself. `@bufbuild/protoplugin` is described as a framework for writing your own Protobuf plugins in TypeScript, and the repository includes `packages/protoplugin-test` and `packages/protoplugin-example`, the latter being an example plugin that generates Twirp clients.

That example is the interesting one for anyone with a legacy service. Twirp is a different RPC framework from Connect or gRPC, and the point of the example is not the protocol but the demonstration: if you have an internal codegen need, you write it in TypeScript against a supported framework rather than writing a plugin in Go or Python and maintaining that toolchain separately.

The packages split into three, which is the map of the repository's `packages/` directory. `@bufbuild/protobuf` is the runtime with message APIs, well-known types, JSON, reflection, registries and extensions. `@bufbuild/protoc-gen-es` is the standard plugin for TypeScript and JavaScript. `@bufbuild/protoplugin` is the generator framework. Reflection, registries and custom options live in the runtime package rather than the plugin, so custom options written into your `.proto` are resolvable at run time.

The companion libraries are separate dependencies. `@connectrpc/connect` handles RPC across Connect, gRPC and gRPC-Web, and `@bufbuild/protovalidate` handles validation. Neither is required to use the Protobuf runtime on its own.

Compatibility floor, maintenance and licensing

The support matrix is explicit, which is unusual and welcome. Baseline web browsers from the last 2.5 years are supported. All maintained Node.js releases are supported. The latest Deno LTS release is supported, and the latest Bun v1 release is supported. TypeScript versions less than 2 years old are supported with default compiler settings.

That last clause has teeth: supported with default compiler settings means an unusual tsconfig is outside the promise. If your project runs a hardened compiler setup, the type-level behaviour is on you.

The development toolchain in the root `package.json` is stricter than the runtime floor. `engineStrict` is true with `node: >=22` and `npm: >=10`, `packageManager` is pinned to `[email protected]`, and `postinstall` runs a nohoist check. Formatting and linting both go through Biome, with `lint` set to `--error-on-warnings`. The package itself is private and marked `type: module`, so the root manifest describes the monorepo, not something you install.

Release cadence is steady: v2.16.0 on 2026-09-29, v2.15.0 on 2026-09-11 and v2.14.1 on 2026-08-31, with the last push on 2026-10-14. Recent releases include v2.16.0 on 2026-09-29, v2.15.0 on 2026-09-11 and v2.14.1 on 2026-08-31.

Licensing has one wrinkle. The repository is Apache-2.0, but the code to encode and decode varint, at `packages/protobuf/src/wire/varint.ts`, is Copyright 2008 Google Inc. under BSD-3-Clause, with all other files under Apache-2.0. Apache-2.0 and BSD-3-Clause are both permissive and compatible for most uses, but a compliance review that scans for a single licence across the tree will need to handle that file separately. That is a description of the licences, not legal advice.

Editorial conclusion

Adopt protobuf-es when your JavaScript or TypeScript service needs Protobuf with real static types and you are willing to regenerate rather than hand-maintain message classes. Do not adopt it if your existing code depends on instance methods or on a `google-protobuf`-style API surface that other libraries in your dependency tree still expect. Before committing, run the quickstart as written, install `@bufbuild/protoc-gen-es` and `@bufbuild/buf`, and check the generated files in `src/gen` against your conventions: the README lists baseline browsers from the last 2.5 years, maintained Node.js releases, latest Deno LTS and latest Bun v1 as the support floor, so anything outside that matrix is your problem.

Frequently asked questions

How do I install and set up protobuf-es?

The README's quickstart installs `@bufbuild/protobuf` and `@connectrpc/connect` as dependencies, `@bufbuild/protoc-gen-es` and `@bufbuild/buf` as dev dependencies, then runs `npx buf generate`. A `buf.gen.yaml` with `local: protoc-gen-es`, `out: src/gen` and `opt: target=ts` puts the generated types there.

Does protobuf-es need getters and setters on message objects?

No. The features list says plain message objects with no getters/setters, which is why every function takes the schema as its first argument, for example `toBinary(UserSchema, user)`. The tradeoff is that the message object carries no runtime description of its own fields.

How does protobuf-es compare to google-protobuf and protobuf.js?

The README says google-protobuf uses a dated getter/setter API and requires third-party plugins for TypeScript, while protobuf.js is a complicated library with three runtimes and three codegen targets that needs extra configuration to be completely conformant and is not type-safe under some configurations.

Which runtimes and TypeScript versions does protobuf-es support?

The compatibility section lists baseline web browsers from the last 2.5 years, all maintained Node.js releases, the latest Deno LTS and the latest Bun v1. TypeScript versions less than 2 years old are supported with default compiler settings.

Official sources

  1. bufbuild/protobuf-es on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
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/bufbuild-protobuf-es.svg)](https://hysenlabs.com/projects/bufbuild-protobuf-es)

Was this useful?