Skip to content

bufbuild/protobuf-es

The Buf logo

Protobuf-ES

License NPM Version NPM Version NPM Version

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.

Don't take our word for it

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 bigint for 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 workflows
  • protobuf.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.

Example

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;

Comparison

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() ⚠️ Better than Google, but centered on verify(), create(), fromObject(), and toObject()
TypeScript output ✅ Built in ❌ Community-maintained typings and separate generators ⚠️ Separate pbts step from generated JavaScript
Codegen flow ✅ Standard protoc and Buf plugin ⚠️ Standard 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 ⚠️ Runtime supports CommonJS and AMD; conformance runner needs a wrapper for static-module plus es6
Editions ✅ 2024 ⚠️ 2023 in the public runner ⚠️ Public runner says it cannot generate code for Editions
Proto2 extensions ✅ Typed extensions and registry APIs ⚠️ Supported with older extension APIs ❌ Proto2 generation breaks on extensions with groups in the public runner
Oneofs ✅ Discriminated unions ❌ Getter maze plus *Case() enums ⚠️ Virtual oneof field names during object conversion
Generated code readability ✅ Typed User definitions and schema exports ❌ Generated classes with list and map helper methods ⚠️ Generated JavaScript plus separate .d.ts output
Tooling friction ✅ One generator, one runtime ⚠️ Extra TypeScript tooling and older JS conventions pbjs, pbts, skipLibChecks, and custom wrapping in the runner

Quickstart

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/buf

Generate TypeScript with a standard plugin configuration:

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

protoc-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.

Migration

google-protobuf

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.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

Documentation

  • 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.

Packages

Compatibility

  • 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.

Copyright

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.

About

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

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors