Defining Protocol Buffer Messages

1. Creating Message Types

Example: Basic message

message Product {
  string sku = 1;
  string name = 2;
  double price = 3;
  bool in_stock = 4;
}
RuleConvention
Message namePascalCase, singular noun
Field namesnake_case
Enum valueUPPER_SNAKE_CASE, prefixed with enum name

2. Defining Scalar Field Types

Proto TypeGoJavaPython
doublefloat64doublefloat
floatfloat32floatfloat
int32 / int64int32 / int64int / longint
uint32 / uint64uint32 / uint64int / longint
boolboolbooleanbool
stringstringStringstr
bytes[]byteByteStringbytes

3. Assigning Field Numbers

GuidelineRationale
1–15 for hot fieldsSingle-byte tag
Never reuse numbersBreaks wire compatibility
Use reserved 4, 7;Prevents future reuse
Sequential, not sparseEasier to manage

4. Using Optional Fields

Example: Presence tracking

message UpdateUserRequest {
  string id = 1;
  optional string email = 2;     // can distinguish "not set" from ""
  optional int32 age = 3;
}
FeatureSingularoptional
Presence on wireOnly if non-defaultAlways when set
HasField APINot availableAvailable
Use caseRequired-ish dataPatch/Update RPCs

5. Using Repeated Fields

Example: Lists

message Cart {
  repeated string product_ids = 1;
  repeated LineItem items = 2;        // [packed=true] is default for scalars
}
AspectNote
OrderPreserved
Packed encodingDefault for scalar repeated in proto3
Empty vs unsetIndistinguishable

6. Defining Nested Messages

Example: Nested types

message Order {
  message LineItem {
    string sku = 1;
    int32 qty = 2;
  }
  repeated LineItem items = 1;
}
// Referenced externally as Order.LineItem
Use CaseChoice
Tightly coupled typeNest inside parent
Reused across messagesTop-level message

7. Creating Enumerations

Example: Enum with safe default

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;   // MUST be 0
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_PAID = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_CANCELED = 4;
}
RuleDetail
First valueMust be 0 and represent UNSPECIFIED
NamingPrefix values with enum name to avoid collisions
allow_alias = truePermits duplicate numbers (aliases)

8. Using Reserved Fields

Example: Reserve removed fields

message User {
  reserved 2, 5, 9 to 11;
  reserved "legacy_token", "old_email";
  string id = 1;
  string email = 3;
}
ReservePrevents
NumbersAccidental wire-format reuse
NamesAccidental JSON/text mapping collision

9. Defining Map Fields

Example: Map field

message Request {
  map<string, string> headers = 1;
  map<int32, Product> products_by_id = 2;
}
ConstraintDetail
Key typesAny integral or string (no float, bytes, message)
Value typesAny except another map
OrderUnordered
repeatedNot allowed

10. Using Oneof Fields

Example: Mutually exclusive payload

message Notification {
  string user_id = 1;
  oneof channel {
    EmailPayload email = 2;
    SmsPayload sms = 3;
    PushPayload push = 4;
  }
}
PropertyDetail
Only one field setSetting another clears the previous
Cannot be repeatedUse repeated Wrapper instead
No maps insideWrap in a message

11. Importing Proto Files

Example: Importing well-known + local files

syntax = "proto3";
package user.v1;

import "google/protobuf/timestamp.proto";
import "common/v1/address.proto";

message User {
  string id = 1;
  google.protobuf.Timestamp created_at = 2;
  common.v1.Address address = 3;
}
TipDetail
Use fully-qualified namesDisambiguates across packages
Avoid cyclesprotoc rejects circular imports

12. Setting Package Names

ConventionExample
Versioned packagecompany.product.v1
Directory layoutMirrors package: proto/company/product/v1/*.proto
Bumping versionCopy to v2/ for breaking changes — never edit v1