100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Protocol Buffers

Defining a .proto File

A practical guide to writing a .proto file: syntax declarations, messages, services, field rules, and naming conventions.

gRPC FoundationsBeginner9 min readJul 10, 2026
Analogies

Defining a .proto File

A .proto file begins with a syntax declaration, almost always syntax = "proto3"; today, followed by an optional package declaration that namespaces your messages to avoid collisions with other .proto files in a large codebase, and language-specific options like go_package or java_package that control where generated code lands. After that setup, the bulk of the file is made of message definitions describing data shapes and service definitions describing the callable RPC methods, and by convention each field within a message gets both a descriptive name and a unique positive integer tag starting from 1.

🏏

Cricket analogy: The package declaration in a .proto file is like a team naming itself 'Mumbai Indians' in the IPL so its players and stats never get confused with a similarly named club from a different league, keeping every reference unambiguous.

Field Rules and Naming Conventions

In proto3, every field is singular by default unless marked repeated for a list, and fields 1 through 15 should be reserved for your most frequently set fields since they encode using only a single byte of tag overhead, while field numbers 16 and above cost two bytes, making the assignment order a small but real performance decision in high-volume messages. Style convention favors snake_case for field and message field names in the .proto source (like user_id, not userId), because protoc's code generators automatically convert those names into each target language's idiomatic casing, such as userId in Java/JS or UserId in Go.

🏏

Cricket analogy: Giving your top-order batsmen the lowest, most efficient batting slots (1 to 3) so they get the most overs is like assigning your most frequently used fields the low tag numbers 1-15, which cost only one byte to encode.

Defining Services

A service block groups related RPC methods under one logical API, and each rpc line names the method plus its request and response message types, optionally prefixed with the stream keyword on either side to declare server, client, or bidirectional streaming. Good .proto design mirrors good API design: keep services focused on one bounded context (an OrderService shouldn't also handle user authentication), use a top-level request/response wrapper message for every method even when it only has one field today, since that gives you room to add parameters later without changing the method's signature.

🏏

Cricket analogy: Grouping RPC methods under one OrderService is like a franchise having a dedicated batting coach who only handles batting drills, not also fielding and bowling — a focused role rather than one person doing everything.

protobuf
syntax = "proto3";

package orders.v1;

option go_package = "github.com/example/orders/v1;ordersv1";

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
  rpc ListOrders (ListOrdersRequest) returns (stream Order);
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderLine lines = 2;
}

message OrderLine {
  string sku = 1;
  int32 quantity = 2;
}

message CreateOrderResponse {
  Order order = 1;
}

message ListOrdersRequest {
  string customer_id = 1;
}

message Order {
  string order_id = 1;
  string customer_id = 2;
  repeated OrderLine lines = 3;
  string status = 4;
}

Wrapping every RPC method's input and output in dedicated request/response messages (CreateOrderRequest, not a bare string) is a widely recommended convention, since it lets you add new fields to a method later without breaking its signature.

Field numbers 19000 to 19999 are reserved by the Protocol Buffers implementation itself and cannot be used in your own message definitions; the compiler will reject them.

  • A .proto file starts with a syntax declaration, an optional package namespace, and language-specific code generation options.
  • Messages describe data shapes; services group related RPC methods under one bounded context.
  • Reserve field numbers 1-15 for your most frequently set fields, since they encode with just one byte of tag overhead.
  • Use snake_case in .proto field names; code generators convert to each language's idiomatic casing automatically.
  • Wrap every RPC method's parameters in a dedicated request/response message for future extensibility.
  • Field numbers 19000-19999 are reserved by protobuf itself and cannot be used.
  • Keep each service focused on one bounded context rather than mixing unrelated responsibilities.

Practice what you learned

Was this page helpful?

Topics covered

#ProtocolBuffers#GRPCStudyNotes#WebDevelopment#DefiningAProtoFile#Defining#Proto#File#Field#StudyNotes#SkillVeris