Skip to content

In practice, virtually nobody hand-writes serializers. Teams generate code from .proto files. The generated code provides typed message constructors, binary serialization, JSON mapping, and service bindings depending on the plugin.

proto/intro/v1/user.proto
edition = "2024";

package intro.v1;

message User {
  int64 id = 1;
  string name = 2;
  uint32 age = 3;
  float height_cm = 4;
  double weight_kg = 5;
  bool verified = 6;
}

From Contract to Runtime API

This schema defines a User message with six fields. Each field has a type, a generated-code name, and a stable field number used by the binary format.

Once code is generated from this schema, you can:

  • Instantiate: Create User objects in your language with type checking and editor support.
  • Serialize: Convert objects into compact binary buffers for transmission or storage.
  • Validate: Reject data that doesn't match the schema's structure before it reaches application logic.

Generating Code with buf

This guide uses the Buf CLI, which keeps generation declarative with a buf.gen.yaml file: check it into your repo and everyone on the team generates the same output with one command. The remote plugins in this example run on the Buf Schema Registry, so there are no plugin binaries to install locally.

buf.gen.yaml
version: v2
plugins:
  - remote: buf.build/bufbuild/es
    out: web/src/gen
    opt: target=ts
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt: paths=source_relative
GENERATE CODE
$ buf generate

Generating with protoc

protoc is the reference compiler for Protobuf. Plugins (like protoc-gen-es) are installed as executables on your system's PATH and selected with command-line flags.

Install plugins
$ npm install --save-dev @bufbuild/protoc-gen-es
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
Generate code
$ protoc --es_out=web/src/gen --es_opt=target=ts \
    --go_out=gen/go --go_opt=paths=source_relative \
    proto/intro/v1/user.proto

Using the Generated Code

With code generated by protobuf-es, the schema becomes a native TypeScript API.

src/main.ts
import { create, toBinary, toJsonString } from "@bufbuild/protobuf";
import { UserSchema } from "./gen/intro/v1/user_pb";

const user = create(UserSchema, {
  id: 1042n,
  name: "cyber_ninja",
  age: 28,
  verified: true,
});

const bytes = toBinary(UserSchema, user);
const json = toJsonString(UserSchema, user);

console.log("JSON Output:", json);
console.log("Binary Output:", bytes);
Different languages and runtimes

The same schema-first workflow applies across supported languages, but import paths, package names, generated types, and runtime APIs differ by language. Getting-started tutorials for each language are linked under Further Reading below.

Further Reading

Next

Advanced

Imports and editions, field presence, unknown fields, and which schema changes break clients that are already deployed.