Skip to content

An enum is a fixed set of named values. A status field that can be active, deferred, or unspecified is the usual example.

Enums are open by default in Edition 2024. Open means a client that reads a value its schema doesn't list will keep that value instead of dropping it. It writes the same number back out when it re-serializes. This is what lets you add a value to an enum without breaking clients that were built against the older schema.

The first value of an open enum has to be zero. Zero is also what you read when the field was never set on the wire, so the zero value ends up meaning "not set" whether you want it to or not. That's why it's usually named something like STATUS_UNSPECIFIED.

Enum value names are a bit odd. They don't live inside the enum; they live in the scope around it. So if you have two enums in the same package, they can't both have a value named ACTIVE, and the compiler will reject the file if they do. This comes from C++, where the generated values land directly in the surrounding namespace. Prefixing every value with the enum name is the usual way around it. Buf's style guide says to do this and buf lint checks it for you.

The JSON carries the name; the wire carries the number 1.

schema.proto
edition = "2024";

package demo.v1;

enum Status {
  STATUS_UNSPECIFIED = 0;
  STATUS_ACTIVE = 1;
  STATUS_DEFERRED = 2;
}

message User {
  Status current_status = 1;
}
Input JSON (editable)
{
  "currentStatus": "STATUS_ACTIVE"
}
ProtoJSON

Compiling the schema...

Further Reading

Next

Packages

Using Protobuf package declarations to namespace schemas, prevent naming collisions, and map cleanly onto packages and modules in generated code.