gRPC With. Net Proto Types Created: 23 Mar 2026 Updated: 23 Mar 2026

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:

  1. Reuse message types across multiple proto files
  2. Organize your definitions by domain or feature
  3. 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.

syntax = "proto3";

package greet;

message HelloReply {
string message = 1;
}

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: The package name is used inside proto files for referencing types. The csharp_namespace option 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):

import "path/to/other.proto";

Rules:

  1. The path uses forward slashes (/), even on Windows
  2. For internal files, the path is relative to the project root
  3. 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:

import "google/protobuf/any.proto";

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:

message HelloReply {
string message = 1;
google.protobuf.Any external_reference_field = 6;
}

The type is called Any and it lives in the google.protobuf package, so you write google.protobuf.Any.

In C#, this becomes:

using Google.Protobuf.WellKnownTypes;

// The generated property:
public Any ExternalReferenceField { get; set; }

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):

syntax = "proto3";

option csharp_namespace = "IndepthProtobuf.Reference";

package greet.reference;

message ReferenceMessage {
string description = 1;
}

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:

<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
<Protobuf Include="Protos\reference.proto" GrpcServices="Server" />
</ItemGroup>

Step 3: Import it and use the type in another proto file:

import "Protos/reference.proto";

message HelloReply {
string message = 1;
greet.reference.ReferenceMessage internal_reference_field = 7;
}

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:

ConceptUsed WhereExample
package nameInside .proto files when referencing typesgreet.reference.ReferenceMessage
csharp_namespaceIn generated C# codeIndepthProtobuf.Reference.ReferenceMessage

Here's how name resolution works in different scenarios:

ScenarioProto Package NameC# Namespace
Has csharp_namespace optiongreet.referenceIndepthProtobuf.Reference (from the option)
No csharp_namespace optiongoogle.protobufGoogle.Protobuf (PascalCase of package)
Remember: When writing .proto files, always use the package name. The csharp_namespace only 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:

  1. Move the actual content to a new file (new.proto)
  2. Turn the original file (reference.proto) into a relay
  3. All existing importers of reference.proto continue to work — no changes needed

Step 1: Create new.proto with the original content:

syntax = "proto3";

option csharp_namespace = "IndepthProtobuf.Reference";

package greet.reference;

message ReferenceMessage {
string description = 1;
}

Step 2: Replace reference.proto content to make it a relay:

syntax = "proto3";

option csharp_namespace = "IndepthProtobuf.Reference";

import public "Protos/new.proto";

package greet.reference;

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:

<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
<Protobuf Include="Protos\reference.proto" GrpcServices="Server" />
<Protobuf Include="Protos\new.proto" GrpcServices="Server" />
</ItemGroup>

Now greet.proto still imports reference.proto, and everything keeps working.

import vs. import public

Featureimportimport public
Who can use the imported types?Only the file that has the importThe file that has the import and anyone who imports this file
Use caseNormal dependencyRelay / re-export (when moving files)
Transitive?NoYes

8. Complete Working Example

Here is the full setup with all three files working together:

new.proto — the actual message definition:

syntax = "proto3";

option csharp_namespace = "IndepthProtobuf.Reference";

package greet.reference;

message ReferenceMessage {
string description = 1;
}

reference.proto — a relay that re-exports new.proto:

syntax = "proto3";

option csharp_namespace = "IndepthProtobuf.Reference";

import public "Protos/new.proto";

package greet.reference;

greet.proto — uses both external and internal imports:

syntax = "proto3";

import "google/protobuf/any.proto";
import "Protos/reference.proto";

option csharp_namespace = "IndepthProtobuf";

package greet;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
google.protobuf.Any external_reference_field = 6;
greet.reference.ReferenceMessage internal_reference_field = 7;
}

C# service — using both imported types:

using Google.Protobuf.WellKnownTypes;
using IndepthProtobuf.Reference;

public class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(
HelloRequest request, ServerCallContext context)
{
// Create a ReferenceMessage (from internal import)
var refMessage = new ReferenceMessage
{
Description = "This came from reference.proto"
};

// Create an Any (from external import)
var anyField = Any.Pack(refMessage);

return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name,
ExternalReferenceField = anyField,
InternalReferenceField = refMessage
});
}
}

9. Common Mistakes

MistakeWhy It's WrongFix
Using backslashes in import pathsProto imports require Unix-style forward slashesUse import "Protos/reference.proto"; not Protos\reference.proto
Forgetting to register in .csprojThe proto file won't be compiled by gRPC toolsAdd <Protobuf Include="Protos\file.proto" /> to the project file
Using C# namespace in proto filesProto files must use the package name, not the csharp_namespaceWrite greet.reference.ReferenceMessage not IndepthProtobuf.Reference.ReferenceMessage
Using import instead of import public for relayRegular import is not transitive — downstream files won't see the typesUse import public "Protos/new.proto";
Build fails after moving filesOld generated code in bin/obj folders causes conflictsDelete bin and obj folders, then rebuild

10. Summary

FeaturePurposeSyntax
packageNames the proto package (like a namespace)package greet.reference;
importImports types from another proto fileimport "Protos/reference.proto";
import publicRe-exports imported types (relay pattern)import public "Protos/new.proto";
External importUses Google's well-known typesimport "google/protobuf/any.proto";
Fully qualified nameReferences a type from another packagegreet.reference.ReferenceMessage


Share this lesson: