mrkeyoor.com_
Sun 20 Sept 17:55 UTC
npmUtilsupdated 20 Sept 2026

@bufbuild/protobuf review

@bufbuild/protobuf 2.14.0 is the runtime used by code generated from Protocol Buffer schemas for JavaScript and TypeScript. It creates plain message objects, reads and writes the binary wire format, follows the Protobuf JSON mapping, and exposes reflection, registries, extensions, text format, and well-known types. It does not read .proto source at runtime. Release 2.14.0 rebuilt several hot paths: the binary writer now grows one buffer, message creators are compiled and cached, and encoding uses new ASCII and base64 fast paths. The release also removes Node 20 support. In our install, both ESM import and CommonJS require worked and the package supplied its own declarations.

Verdict

@bufbuild/protobuf 2.14.0 installed in 1.5 seconds as one 3 MB package with 0 audit findings, working ESM and CommonJS entry points, and bundled types. It is a good fit for generated TypeScript schemas and Connect code, but protobufjs is the clearer choice when .proto files must be parsed at runtime, and Node 20 deployments cannot take this release.

We installed it

Lab card: what happened when we installed @bufbuild/protobufScreenshot of @bufbuild/protobuf documentation
Install✓ · 1.5s1 package on disk · 3 MB
ImportESM import works · require() works · ESM package with exports map
Browser27.8 KBgzipped (103.4 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @bufbuild/protobuf install cleanly?

Yes. In a fresh container with an empty cache, npm install @bufbuild/protobuf finished in 2 seconds, leaving 1 package and 3 MB on disk. npm audit reported no known vulnerabilities.

How much does @bufbuild/protobuf add to a browser bundle?

27.8 KB gzipped (103.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @bufbuild/protobuf work with both ESM and CommonJS?

Yes. Both import '@bufbuild/protobuf' and require('@bufbuild/protobuf') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does @bufbuild/protobuf include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@bufbuild/protobuf or protobufjs: which should you use?

protobufjs: Use it when the application must parse .proto text or JSON descriptors at runtime. @bufbuild/protobuf 2.14.0 installed in 1.5 seconds as one 3 MB package with 0 audit findings, working ESM and CommonJS entry points, and bundled types.

When should you not use @bufbuild/protobuf?

The process must parse user-supplied .proto text while it runs. This runtime accepts generated schemas or descriptor sets; protobufjs has a runtime parser.

API stability4/5Version 2 has kept its schema-first calls for create(), binary conversion, JSON conversion, cloning, equality, merging, and reflection steady across current releases. Release 2.14.0 changes internal writers and caches without changing those common signatures. The deduction reflects two real compatibility boundaries: the v1-to-v2 migration replaced message instance methods with free functions, and 2.14.0 removed Node 20 support on a minor release.
Docs5/5protobufes.com documents generation, message construction, binary and JSON rules, valid input shapes, well-known types, reflection, registries, extensions, custom options, plugin writing, and the v1 migration. The repository links working examples and public conformance results. The compatibility page also names the supported browser, Node, Deno, Bun, and TypeScript windows, so readers can check deployment assumptions before copying a snippet.
Maintenance5/5GitHub showed an unarchived repository pushed on August 26, 2026, with 1,655 stars and 35 open issues and pull requests. Version 2.14.0 shipped on August 13 with benchmark-backed work on the binary writer, descriptor compilation, message creation, varints, base64, and UTF-8 paths. The release notes identify each merged change, and the same repository maintains the runtime, generator, conformance runner, and examples.
Ecosystem4/5The npm endpoint counted 21,192,775 downloads from August 19 through August 25, 2026. Buf supplies protoc-gen-es for generation, Connect packages for RPC, Protovalidate for validation, and protoplugin for custom generators. That toolchain covers a full schema-driven application. Compatibility is weaker with older grpc-tools examples that expect google-protobuf classes, and runtime schema-loading projects often use protobufjs instead.

Use it if

  • You generate TypeScript from .proto files and want fields exposed as ordinary object properties instead of getter and setter methods.
  • The application needs Protobuf binary and JSON conversion that is tested against the official conformance suite.
  • Reflection, descriptor registries, custom options, extensions, or well-known types need to share one runtime with generated messages.
  • You are building Connect clients or servers and want the schema objects used by the Connect packages.
Skip it if

Setup reality

Our no-cache install of @bufbuild/protobuf 2.14.0 finished in 1.5 seconds inside an unprivileged Node 22 Bookworm container. It left one package and 3 MB on disk. The package was 2,816 KB unpacked, declared 0 direct dependencies and 0 peer dependencies, and npm audit found 0 known vulnerabilities. It is an ESM package with an exports map, yet both require() and ESM import passed. TypeScript declarations ship in the package.

Installing the runtime does not turn .proto files into modules. Add @bufbuild/protoc-gen-es and either Buf or protoc, put the generator in buf.gen.yaml, and regenerate after every schema edit. The generated modules import runtime code, so keep generator and runtime versions compatible. TypeScript output uses target=ts; Node ESM projects commonly set import_extension=js so emitted imports resolve after compilation. None of this needs credentials unless the schemas come from a private registry.

Our esbuild check imported the entire package root and produced 103.4 KB minified, or 27.8 KB gzipped. A real bundle can be smaller when tree shaking removes unused exports, but that depends on the exact imports. Well-known types, reflection, wire helpers, and text format have separate export paths. The install ran without native compilation or a postinstall hook.

Protobuf rules cause the first runtime surprises. A 64-bit integer is represented as bigint, so JSON.stringify cannot serialize a message containing one; use toJson() or toJsonString(). An ordinary proto3 scalar does not record presence unless the schema marks it optional. Dynamic operation requires a compiled descriptor set and createFileRegistry(), which still does not parse .proto text. Version 2.14.0 no longer supports Node 20, so check runtime images before upgrading.

Patterns

Generate TypeScript from local schemas generate-typescript

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

# install and generate
npm install @bufbuild/protobuf
npm install --save-dev @bufbuild/protoc-gen-es @bufbuild/buf
npx buf generate

Version 2 generation emits schema objects used by the runtime functions. import_extension=js is needed by many Node ESM builds.

Create a message from an initializer create-message

import { create } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const user = create(UserSchema, {
  id: 'user-42',
  displayName: 'Mira',
});
user.displayName = 'Mira Singh';

create() takes the generated schema first and returns a plain object with the message metadata expected by the runtime.

Write a message to the wire format encode-binary

import { toBinary } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const bytes = toBinary(UserSchema, user);
await fileHandle.write(bytes);

Version 2 messages do not have a serializeBinary() or toBinary() instance method. Pass the schema and value to the free function.

Decode untrusted binary input decode-binary

import { fromBinary } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

try {
  const user = fromBinary(UserSchema, requestBody);
  console.log(user.id);
} catch (error) {
  return new Response('invalid protobuf', { status: 400 });
}

fromBinary() throws on malformed wire data. Catch that failure at the network or file boundary.

Use the Protobuf JSON mapping read-write-json

import { fromJsonString, toJsonString } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const json = toJsonString(UserSchema, user);
const decoded = fromJsonString(UserSchema, json);

These helpers apply Protobuf rules for field names, enums, bytes, and 64-bit integers. JSON.stringify() cannot handle bigint values.

Set a 64-bit integer without losing precision set-int64

import { create, protoInt64 } from '@bufbuild/protobuf';
import { InvoiceSchema } from './gen/invoice_pb.js';

const invoice = create(InvoiceSchema, {
  sequence: protoInt64.parse('9007199254740993'),
});

64-bit integer fields use bigint. protoInt64.parse() checks the string value against the field's supported range.

Distinguish an optional field from its default check-presence

import { clearField, isFieldSet } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const field = UserSchema.field.nickname;
if (isFieldSet(user, field)) {
  console.log(user.nickname);
}
clearField(user, field);

Presence exists only when the schema supports it. Mark a proto3 scalar optional if unset must differ from its zero value.

Copy and compare messages clone-compare

import { clone, equals } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const copy = clone(UserSchema, user);
copy.displayName = 'Changed';
console.log(equals(UserSchema, user, copy));

equals() compares message values, including byte arrays by content. clone() prevents edits to the copy from mutating the original.

Merge a message patch into a copy merge-patch

import { clone, merge } from '@bufbuild/protobuf';
import { UserSchema } from './gen/user_pb.js';

const next = clone(UserSchema, current);
merge(UserSchema, next, incomingPatch);

merge() mutates the target and follows Protobuf merge rules. Repeated fields append instead of replacing the existing array.

Convert between Timestamp and Date convert-timestamp

import { timestampDate, timestampFromDate } from '@bufbuild/protobuf/wkt';

const sentAt = timestampFromDate(new Date('2026-08-26T10:00:00Z'));
const nativeDate = timestampDate(sentAt);

Timestamp helpers are exported from /wkt. JavaScript Date stores milliseconds, so nanosecond precision is lost on conversion.

Pack and verify an Any value pack-any

import { anyIs, anyPack, anyUnpack } from '@bufbuild/protobuf/wkt';
import { UserSchema } from './gen/user_pb.js';

const packed = anyPack(UserSchema, user);
if (anyIs(packed, UserSchema)) {
  const unpacked = anyUnpack(packed, UserSchema);
  console.log(unpacked?.id);
}

Unpacking by dynamic type name requires a registry containing that schema. anyUnpack() returns undefined when the type does not match.

Build a reflection registry from descriptors load-descriptor-set

import { createFileRegistry, fromBinary } from '@bufbuild/protobuf';
import { FileDescriptorSetSchema } from '@bufbuild/protobuf/wkt';
import { readFile } from 'node:fs/promises';

const bytes = await readFile('schema.binpb');
const descriptorSet = fromBinary(FileDescriptorSetSchema, bytes);
const registry = createFileRegistry(descriptorSet);
const schema = registry.getMessage('acme.user.v1.User');

Compile schema.binpb before deployment. A descriptor registry supports reflection but does not create static TypeScript types or parse .proto source.

Alternatives

PackageRegistryPick it when
protobufjsnpmUse it when the application must parse .proto text or JSON descriptors at runtime.
google-protobufnpmKeep it when a mature codebase already uses protoc's JavaScript message classes and getter APIs.
@protobuf-ts/runtimenpmChoose it when protobuf-ts already generates the project and its reflection model is part of the design.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.