Referencing Other Proto Files — A Beginner's Guide
1. Why Reference Other Proto Files?
As your project grows, putting everything into a single .proto file becomes messy. You want to split your definitions into separate files, just like you'd split C# code into separate classes and namespaces. Protobuf's import system lets you:
- Reuse message types across multiple proto files
- Organize your definitions by domain or feature
- Use external libraries like Google's well-known types (Timestamp, Any, Duration, etc.)
2. The package Directive
Think of the package directive as a namespace for your proto file. It gives your messages a unique "address" so that other proto files can refer to them without name collisions.
Now, any other proto file that imports this file must use the fully qualified name greet.HelloReply to refer to the HelloReply message.
Key point: Thepackagename is used inside proto files for referencing types. Thecsharp_namespaceoption controls the C# namespace in generated code — they are independent of each other.
3. The import Directive
To use types from another proto file, you add an import statement at the top of your file (before the package directive):
Rules:
- The path uses forward slashes (
/), even on Windows - For internal files, the path is relative to the project root
- For well-known types (Google packages), the paths are pre-configured in the gRPC tools
4. Importing External Proto Packages
Google publishes a set of commonly used proto types called well-known types. One of them is Any, which can hold any arbitrary message type. Let's import it.
Step 1: Add the import at the top of your proto file:
You don't need to download anything — the gRPC tools already know where to find Google's published proto files.
Step 2: Use the type with its fully qualified name:
The type is called Any and it lives in the google.protobuf package, so you write google.protobuf.Any.
In C#, this becomes:
Notice the namespace: Google.Protobuf.WellKnownTypes. Since the original proto package (google.protobuf) has no csharp_namespace option, the C# namespace is created by converting the package name to PascalCase.
5. Referencing Internal Proto Files
You can also create your own reusable proto files within the same project. Here's how:
Step 1: Create a new proto file (e.g., reference.proto):
This file defines a ReferenceMessage in the greet.reference package. It has no service definition — it's a pure data-only file meant to be referenced by others.
Step 2: Register it in the .csproj file:
Step 3: Import it and use the type in another proto file:
You reference ReferenceMessage using its fully qualified proto name: greet.reference.ReferenceMessage.
6. Package Name vs. C# Namespace
This is a critical distinction that confuses many beginners. There are two separate naming systems at play:
| Concept | Used Where | Example |
|---|---|---|
package name | Inside .proto files when referencing types | greet.reference.ReferenceMessage |
csharp_namespace | In generated C# code | IndepthProtobuf.Reference.ReferenceMessage |
Here's how name resolution works in different scenarios:
| Scenario | Proto Package Name | C# Namespace |
|---|---|---|
Has csharp_namespace option | greet.reference | IndepthProtobuf.Reference (from the option) |
No csharp_namespace option | google.protobuf | Google.Protobuf (PascalCase of package) |
Remember: When writing.protofiles, always use thepackagename. Thecsharp_namespaceonly matters when you write C# code that uses the generated classes.
7. Using Proto Files as Relays (import public)
Imagine you need to move a proto file to a different location (perhaps to share it with another project). Normally, every file that imports it would break because the path changed. Protobuf solves this with import public.
How it works:
- Move the actual content to a new file (
new.proto) - Turn the original file (
reference.proto) into a relay - All existing importers of
reference.protocontinue to work — no changes needed
Step 1: Create new.proto with the original content:
Step 2: Replace reference.proto content to make it a relay:
The import public directive makes reference.proto act as if the content of new.proto is its own. Any file that imports reference.proto will implicitly get everything from new.proto.
Step 3: Register new.proto in the .csproj:
Now greet.proto still imports reference.proto, and everything keeps working.
import vs. import public
| Feature | import | import public |
|---|---|---|
| Who can use the imported types? | Only the file that has the import | The file that has the import and anyone who imports this file |
| Use case | Normal dependency | Relay / re-export (when moving files) |
| Transitive? | No | Yes |
8. Complete Working Example
Here is the full setup with all three files working together:
new.proto — the actual message definition:
reference.proto — a relay that re-exports new.proto:
greet.proto — uses both external and internal imports:
C# service — using both imported types:
9. Common Mistakes
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Using backslashes in import paths | Proto imports require Unix-style forward slashes | Use import "Protos/reference.proto"; not Protos\reference.proto |
Forgetting to register in .csproj | The proto file won't be compiled by gRPC tools | Add <Protobuf Include="Protos\file.proto" /> to the project file |
| Using C# namespace in proto files | Proto files must use the package name, not the csharp_namespace | Write greet.reference.ReferenceMessage not IndepthProtobuf.Reference.ReferenceMessage |
Using import instead of import public for relay | Regular import is not transitive — downstream files won't see the types | Use import public "Protos/new.proto"; |
| Build fails after moving files | Old generated code in bin/obj folders causes conflicts | Delete bin and obj folders, then rebuild |
10. Summary
| Feature | Purpose | Syntax |
|---|---|---|
package | Names the proto package (like a namespace) | package greet.reference; |
import | Imports types from another proto file | import "Protos/reference.proto"; |
import public | Re-exports imported types (relay pattern) | import public "Protos/new.proto"; |
| External import | Uses Google's well-known types | import "google/protobuf/any.proto"; |
| Fully qualified name | References a type from another package | greet.reference.ReferenceMessage |