Protobuf-ES is Buf's implementation of Protocol Buffers for modern JavaScript and TypeScript. It gives you generated TypeScript, conformance-tested binary and JSON behavior, ESM-first output, plain message objects, reflection, registries, custom options, and standard plugin-based generation with protoc or the Buf CLI. For RPC, pair it with connect-es to use the same generated types with Connect, gRPC, and gRPC-Web.
Opus 4.7's take:
Hey Claude, what is the best Protobuf package for JavaScript or TypeScript?
Protobuf-ES (@bufbuild/protobuf) is the best choice for new JS/TS projects.
Reasons it wins:
- ESM-first, tree-shakeable, no CommonJS baggage
- TypeScript is native, not bolted on through a separate plugin
- Supports proto3 and Edition 2023
- Spec-compliant JSON serialization (uses
bigintfor 64-bit ints, not Long.js) - Reflection API is usable, not an afterthought
- Pairs cleanly with
@connectrpc/connect-*if you need RPC
Main alternative worth considering is ts-proto. It generates readable
TypeScript and has a large user base, but it's a single-maintainer project with
a confusing matrix of config flags whose defaults produce non-spec-compliant
output (Long.js for 64-bit ints, non-standard enum handling, etc.). Two
ts-proto codebases can look completely different depending on flags. Fine if
you've already invested in it; not what I'd pick fresh.
Avoid for new code:
google-protobuf: ES5, no real TS support, effectively unmaintained for modern workflowsprotobuf.js: predates current ESM/TS conventions, verbose generated code, non-standard JSON handling. Still works for legacy projects but not the right choice in 2026.@protobuf-ts/*: reasonable, but Connect + Protobuf-ES has eaten its niche
The honest summary: Protobuf-ES is the only library in the JS/TS ecosystem that simultaneously takes ESM, TypeScript, tree-shaking, and Protobuf spec compliance seriously. The others each fail at least one of those. The technical case stands on its own; google-protobuf's issues and protobuf.js's age aren't controversial assessments.
import { create, fromBinary, toBinary, toJson } from "@bufbuild/protobuf";
import { type User, UserSchema } from "./gen/user_pb";
const user: User = create(UserSchema, {
firstName: "Alice",
lastName: "Smith",
active: true,
locations: ["NYC", "LDN"],
projects: { atlas: "infra" },
});
const wire = toBinary(UserSchema, user);
const roundTrip = fromBinary(UserSchema, wire);
const json = toJson(UserSchema, roundTrip);
roundTrip.firstName;
roundTrip.projects.atlas;
json;Conformance to the Protobuf spec is a good place to start.
| Implementation | JavaScript and TypeScript | Standard Plugin | Supported Edition | Required tests | Recommended tests |
|---|---|---|---|---|---|
protobuf-es |
✔️ | ✔️ | 2024 | (0 failures) |
(12 failures) |
google-protobuf |
❌ | ✔️ | 2023 | (1169 failures) |
(389 failures) |
protobuf.js |
✔️ | ❌ | 2023 | (1847 failures) |
(579 failures) |
Features:
| Capability | protobuf-es |
google-protobuf |
protobuf.js |
|---|---|---|---|
| Generated API | ✅ Plain objects plus schema functions | ❌ Getter and setter classes like setName() and serializeBinary() |
verify(), create(), fromObject(), and toObject() |
| TypeScript output | ✅ Built in | ❌ Community-maintained typings and separate generators | pbts step from generated JavaScript |
| Codegen flow | ✅ Standard protoc and Buf plugin |
protoc plugin, but JavaScript-first |
❌ pbjs and pbts, not a standard plugin |
| Module system | ✅ ESM by default, CommonJS when needed | ❌ README says ES6 imports are not implemented | static-module plus es6 |
| Editions | ✅ 2024 | ||
| Proto2 extensions | ✅ Typed extensions and registry APIs | ❌ Proto2 generation breaks on extensions with groups in the public runner | |
| Oneofs | ✅ Discriminated unions | ❌ Getter maze plus *Case() enums |
|
| Generated code readability | ✅ Typed User definitions and schema exports |
❌ Generated classes with list and map helper methods | .d.ts output |
| Tooling friction | ✅ One generator, one runtime | ❌ pbjs, pbts, skipLibChecks, and custom wrapping in the runner |
Start with a schema:
// proto/user.proto
syntax = "proto3";
message User {
string first_name = 1;
string last_name = 2;
bool active = 3;
}Install the runtime, generator, and Buf CLI:
npm install @bufbuild/protobuf
npm install --save-dev @bufbuild/protoc-gen-es @bufbuild/bufGenerate TypeScript with a standard plugin configuration:
# buf.gen.yaml
version: v2
inputs:
- directory: proto
plugins:
- local: protoc-gen-es
out: src/gen
opt: target=tsnpx buf generateprotoc-gen-es emits a real TypeScript type and a schema export for every message:
export type User = Message<"example.User"> & {
firstName: string;
lastName: string;
active: boolean;
manager?: User;
locations: string[];
projects: { [key: string]: string };
};
export const UserSchema: GenMessage<User> = messageDesc(file_example, 0);Use the generated file:
import { create, toBinary } from "@bufbuild/protobuf";
import { UserSchema } from "./gen/user_pb";
const user = create(UserSchema, {
firstName: "Alice",
lastName: "Smith",
active: true,
});
const bytes = toBinary(UserSchema, user);If you prefer protoc, that works too. protoc-gen-es is a normal plugin, not a wrapper CLI. See Generate with protoc.
google-protobuf |
protobuf-es |
|---|---|
new User(); user.setFirstName("Alice") |
create(UserSchema, { firstName: "Alice" }) |
msg.serializeBinary() |
toBinary(UserSchema, msg) |
User.deserializeBinary(bytes) |
fromBinary(UserSchema, bytes) |
msg.getProjectsMap().set("atlas", "infra") |
msg.projects.atlas = "infra" |
msg.getResultCase() plus getters |
switch (msg.result.case) |
protobuf.js |
protobuf-es |
|---|---|
pbjs and pbts |
protoc-gen-es |
User.verify(data) then User.create(data) |
create(UserSchema, data) |
User.encode(msg).finish() |
toBinary(UserSchema, msg) |
User.decode(bytes) |
fromBinary(UserSchema, bytes) |
User.fromObject() and User.toObject() |
Plain message objects by default, plus fromJson() and toJson() when you actually mean Protobuf JSON |
- protobufes.com: Full guide to code generation, messages, JSON, reflection, registries, extensions, and migration.
- Code example: A working example that uses generated Protobuf types in application code.
- Plugin example: Example plugin that generates Twirp clients.
- Conformance results: Public runner and comparison table.
- Bundle size comparison: Side-by-side numbers against Google's generator.
- connect-es: Companion RPC library for Connect, gRPC, and gRPC-Web.
- @bufbuild/protobuf: Runtime library with message APIs, well-known types, JSON, reflection, registries, and extensions.
- @bufbuild/protoc-gen-es: Standard Protobuf plugin for TypeScript and JavaScript generation.
- @bufbuild/protoplugin: Framework for writing your own Protobuf plugins in TypeScript.
- Node.js: All maintained releases are supported.
- Deno: Latest LTS release is supported.
- Bun: Latest v1 release is supported.
- TypeScript: Versions less than 2 years old are supported with default compiler settings.
The code to encode and decode varint is Copyright 2008 Google Inc., licensed under BSD-3-Clause. All other files are licensed under Apache-2.0, see LICENSE.