Serialization
Protobuf-ES uses the same generated schema for wire-compatible binary and ProtoJSON, so both formats stay tied to the same types. Binary is the default for network traffic and storage: faster, preserves unknown fields, and handles schema evolution well. JSON is useful for debugging and for integrations that already speak JSON.
Serialize and parse
Section titled “Serialize and parse”Use toBinary() and fromBinary() for the Protobuf wire format:
import { fromBinary, toBinary } from "@bufbuild/protobuf";import { type User, UserSchema } from "./gen/example_pb";
declare let user: User;
const bytes: Uint8Array = toBinary(UserSchema, user);user = fromBinary(UserSchema, bytes);Use toJson() and fromJson() for ProtoJSON values:
import { fromJson, toJson, type JsonValue } from "@bufbuild/protobuf";import { type User, UserSchema } from "./gen/example_pb";
declare let user: User;
const json: JsonValue = toJson(UserSchema, user);user = fromJson(UserSchema, json);If you want strings instead of JsonValue, use toJsonString() and fromJsonString(). To parse into an existing message, use mergeFromBinary() or mergeFromJson().
Binary serialization options
Section titled “Binary serialization options”toBinary() accepts one option:
writeUnknownFields?: boolean: Include unknown fields in the serialized output. By default, unknown fields are preserved and written back out.
fromBinary() accepts one option:
readUnknownFields?: boolean: Retain unknown fields while parsing. By default, unknown fields are kept.
JSON serialization options
Section titled “JSON serialization options”fromJson() and fromJsonString() accept:
ignoreUnknownFields?: boolean: Ignore unknown properties and unknown enum string values instead of rejecting them.registry?: Registry: Use a registry when parsinggoogle.protobuf.Anyand extensions from JSON.
toJson() and toJsonString() accept:
alwaysEmitImplicit?: boolean: Emit fields with implicit presence even when they hold default values.enumAsInteger?: boolean: Write enum numbers instead of enum names.useProtoFieldName?: boolean: Use original proto field names instead oflowerCamelCaseJSON names.registry?: Registry: Use a registry forgoogle.protobuf.Anyand extensions.prettySpaces?: number: Only fortoJsonString(). Passed through toJSON.stringify().
See Registries for creating registries, Any with registries for a complete Any example, and Plugin options if you want generated JSON types.
Unknown fields
Section titled “Unknown fields”When Protobuf-ES parses binary data, unrecognized fields are stored on the message as $unknown?: UnknownField[] | undefined. When the message is serialized again, those fields are preserved by default.
Extensions use the same storage under the hood.
Binary encoding
Section titled “Binary encoding”The public BinaryReader and BinaryWriter classes implement the low-level Protobuf wire format.
import { BinaryWriter, WireType } from "@bufbuild/protobuf/wire";import { fromBinary } from "@bufbuild/protobuf";import { UserSchema } from "./gen/example_pb";
const bytes = new BinaryWriter() .tag(1, WireType.LengthDelimited) .string("Homer") .tag(3, WireType.Varint) .bool(true) .finish();
const user = fromBinary(UserSchema, bytes);Use the message-level helpers unless you are working directly with the wire format.
Text encoding
Section titled “Text encoding”Protobuf-ES uses the WHATWG Text Encoding API to convert UTF-8 to and from binary.
If your environment does not provide the API, call configureTextEncoding() from @bufbuild/protobuf/wire early during initialization and supply your own implementation.
Base64 encoding
Section titled “Base64 encoding”Use the helpers from @bufbuild/protobuf/wire when you need a portable Base64 representation of binary data:
import { base64Encode, base64Decode } from "@bufbuild/protobuf/wire";
base64Encode(new Uint8Array([2, 4, 8, 16])); // "AgQIEA=="base64Decode("AgQIEA=="); // Uint8Array(4)Size-delimited message streams
Section titled “Size-delimited message streams”Protobuf-ES supports the size-delimited format used to write multiple messages to a stream.
A size-delimited message is a varint length prefix followed by that many bytes of a normal binary Protobuf message.
Serialize with sizeDelimitedEncode():
import { sizeDelimitedEncode } from "@bufbuild/protobuf/wire";import { type User, UserSchema } from "./gen/example_pb";import { createWriteStream } from "node:fs";
declare const user: User;
const stream = createWriteStream("delim.bin", { encoding: "binary" });stream.write(sizeDelimitedEncode(UserSchema, user));stream.end();Parse a stream with sizeDelimitedDecodeStream():
import { sizeDelimitedDecodeStream } from "@bufbuild/protobuf/wire";import { createReadStream } from "node:fs";import { UserSchema } from "./gen/example_pb";
const stream = createReadStream("delim.bin");for await (const user of sizeDelimitedDecodeStream(UserSchema, stream)) { console.log(user);}This format is compatible with the delimited message support in the C++, Java, Go, and other Protobuf runtimes.