Skip to content

Once a project has a few schemas in it you will start running into messages with the same name. Protobuf uses a package declaration to keep them apart.

The package name becomes part of the type name. The Account message in the example is really demo.identity.v1.Account, and that longer name is the one that matters. Other files use it to refer to the message. The Any type stores it in a type URL. Reflection looks it up at runtime. Inside the same package you can just write Account and leave the rest off.

Put the v1 on the end from the start. It is the major version. When you need to make a breaking change later you write a new package ending in v2 and leave the old one alone, so clients that are still on v1 keep working. Buf's style guide has this, and buf lint checks it, along with a related rule that the file path should match the package: demo/identity/v1/account.proto.

In generated code the package turns into whatever that language uses for namespacing. C++ gets a namespace. Java and Go get packages. TypeScript gets modules. Some languages don't map cleanly, Go import paths being the usual example, so there are file-level options like go_package to steer it.

Buf's files and packages reference goes through how packages, file paths, and directories relate in more detail.

edition = "2024";

// Defines the namespace. The file lives at
// demo/identity/v1/account.proto to match.
package demo.identity.v1;

import "demo/billing/v1/invoice.proto";

// Full name: demo.identity.v1.Account
message Account {
  string id = 1;

  // A type from another package needs its
  // qualified name.
  demo.billing.v1.Invoice last_invoice = 2;
}

Further Reading

Next

Composition

Building complex Protobuf data models by nesting message definitions and reusing messages as field types, and how embedded messages encode on the wire.