Skip to content

Enums, Nested & oneof

An enum is a field that can only be one of a fixed set of named integers. It documents intent and prevents invalid states far better than a bare int32 or string.

enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_SUSPENDED = 2;
STATUS_CLOSED = 3;
}
message Account {
int64 id = 1;
Status status = 2;
}

There is one rule you cannot skip: the first enum value must be 0, and it should mean “unspecified.” Because proto3 has no null for scalars, an unset enum field reads as its zero value. If 0 meant ACTIVE, every account that forgot to set a status would silently look active.

flowchart TB
  unset["field not set"] --> zero["reads as value 0"]
  zero --> good["0 = UNSPECIFIED
→ safe, obviously missing"]
  zero --> bad["0 = ACTIVE
→ dangerous, looks intentional"]
Why the zero value must mean 'unspecified'

The convention (used across Google’s APIs) is to name it <ENUM>_UNSPECIFIED = 0. It gives you a clear “no value” state and reserves the meaningful values for explicit choices. Adding new enum values later is a safe, backward-compatible change — old clients simply treat unknown values as unknown.

Messages can contain other messages. Nesting groups related data and lets you reuse a type, or scope a type to the message that owns it:

message Order {
int64 id = 1;
message LineItem { // scoped to Order (Order.LineItem)
string sku = 1;
int32 quantity = 2;
}
repeated LineItem items = 2;
Address shipping = 3; // a top-level message reused here
}

Nest a type when it only makes sense inside its parent (Order.LineItem); keep it top-level when several messages share it (Address).

A oneof says “at most one of these fields is set at a time.” Setting one clears the others, and the generated code gives you a clean way to check which is present.

message Notification {
int64 user_id = 1;
oneof channel {
string email = 2;
string sms_number = 3;
string push_token = 4;
}
}

Here a notification goes out over exactly one channel. oneof models that “tagged union” precisely — far better than three nullable fields and a comment hoping only one is filled in. It’s also memory-efficient: the fields share storage.

Why must the first enum value be 0 and mean "unspecified"?
When should a message be nested inside another rather than top-level?
What does a `oneof` guarantee?
Adding a new value to an existing enum is: