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. protoc works the same way once you tell it where the roots are: every -I (or --proto_path) flag names one, and if two files spell the same import differently, such as import "proto/user.proto" in one and import "user.proto" in another, protoc treats them as two different types.
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. Vendored copies don't record which version they came from.
With a schema registry, the other project is a declared dependency. The Buf Schema Registry pins each one to an exact version in a lockfile, so every checkout resolves imports identically, the same way npm and Cargo handle package dependencies. The buf.yaml file in the example declares buf.build/bufbuild/protovalidate as a dependency, so service.proto can import buf/validate/validate.proto.
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.