The import statement brings definitions from another .proto file into scope. Paths resolve from your module root rather than relative to the file doing the importing. Always write the full path from that root, so the same import string refers to the same file no matter which .proto it appears in.
Often the definitions you want already live in another project. You can vendor them: copy that team's .proto files into your repository, then copy them again whenever they change. Nothing tracks which version you copied.
A schema registry makes it a declared dependency instead. The Buf Schema Registry pins each one to an exact version in a lockfile, so every checkout resolves imports identically. It's the model npm and Cargo use, applied to schemas. The buf.yaml tab in the example declares buf.build/bufbuild/protovalidate as a dependency, which is what lets service.proto import buf/validate/validate.proto.
protoc include paths (-I / --proto_path)
The protoc compiler requires manually specifying every include directory via -I (or --proto_path) flags. If import paths are inconsistent across your project (e.g. import "proto/user.proto" vs import "user.proto"), protoc treats them as different types.
edition = "2024"; package auth.v1; import "buf/validate/validate.proto"; import "common/v1/user.proto"; import "google/type/datetime.proto"; message LoginRequest { string email = 1 [(buf.validate.field).string.email = true]; } message LoginResponse { common.v1.User user = 1; string session_token = 2; google.type.DateTime expires_at = 3; }
buf dep update buf build
Further Reading
- Tip of the week #5: Avoid import public/weak
Two special import modes, and why Buf lints against both by default.
Next
The Any Type
Using google.protobuf.Any to embed messages whose schema isn't known at compile time, with type URLs and the special @type property in ProtoJSON.