Skip to content

Protobuf Editions replaces the old proto2 and proto3 syntax switch with individual feature settings. Instead of upgrading a whole file at once, you can change behaviors one at a time:

  • Field Presence: whether a reader can tell a field that was never set apart from one set to its default value. Choose IMPLICIT (the proto3 behavior) or EXPLICIT (the proto2 behavior); the Field Presence page covers the tradeoff.
  • Enum Type: where a value the enum does not declare ends up. OPEN keeps it in the field; CLOSED moves it to the unknown fields.
  • Repeated Encoding: Standardize on PACKED (for efficiency) or EXPANDED (for compatibility).

Under proto2 and proto3, these behaviors came as a set with the syntax keyword at the top of the file, so migration was all or nothing. Moving a file to proto3 opened its closed enums, dropped its custom field defaults, and removed explicit presence from its singular scalars.

Editions makes each of those a separate feature. A feature can be set for a whole file and then overridden on the elements it applies to, such as one enum for enum type or one field for field presence, so a schema can adopt one new behavior without taking the others. New behavior ships as a new feature with a per-edition default, so there is no "proto4" forcing another migration of every behavior at once.

Three editions have shipped so far: edition 2023, which unified proto2 and proto3; edition 2024, which added symbol visibility, import option, and naming-style enforcement; and edition 2026 (protobuf 36.x), which tightens the naming rules, defaults symbol visibility to strict, and adds custom JSON names for enum values. The editions overview and the feature settings reference track exactly what each edition changes.

edition = "2024";

package demo.v1;

// Set for the whole file...
option features.enum_type = CLOSED;

enum Unit {
  UNIT_UNSPECIFIED = 0;
  UNIT_CELSIUS = 1;
}

enum Status {
  // ...and overridden on one enum.
  option features.enum_type = OPEN;

  STATUS_UNSPECIFIED = 0;
  STATUS_ACTIVE = 1;
}

Edition by edition

No edition changes the wire format. A field is encoded the same way under proto3, edition 2023, and edition 2026, and a reader on any of them can parse what a writer on any other produced. The bytes can still differ where the defaults do: a field explicitly set to zero is written under explicit presence, the editions default, and skipped under proto3's implicit presence. Editions change defaults and language rules instead, such as how an unset field behaves, which schemas the compiler accepts, and the shape of the generated code. Moving a file forward is an edit to its first line, plus explicit features wherever the new defaults would change the file's behavior; the compiler reports anything it cannot carry over.

2008 and 2016

Before editions: proto2 and proto3

The syntax line at the top of a file chose between two fixed sets of behavior.

  • proto2 has explicit presence on every singular field, closed enums, required, custom default values, extensions on any message, and groups.
  • proto3 drops required, custom defaults, and general extensions. Scalar fields get implicit presence, enums are open, repeated scalars are packed, and the JSON mapping is standard. The optional keyword came back in 2021 to give a proto3 scalar explicit presence again.

The first edition

Edition 2023

Replaces the syntax switch with features. Each behavior proto2 and proto3 disagreed on becomes a setting with a default, and the default can be changed for a whole file or on the individual elements each feature applies to.

  • features.field_presence: EXPLICIT by default, so every singular field can tell unset from zero without optional. IMPLICIT restores the proto3 behavior; LEGACY_REQUIRED is the editions form of the proto2 required label.
  • features.enum_type: OPEN by default, the proto3 behavior. CLOSED keeps proto2 enums working as they did.
  • features.repeated_field_encoding: PACKED by default; EXPANDED writes a tag per element.
  • features.utf8_validation (VERIFY), features.message_encoding (LENGTH_PREFIXED, with DELIMITED standing in for proto2 groups), and features.json_format (ALLOW) cover the rest.
  • The optional and required keywords are gone, and extensions are allowed on any message again.

The edition this guide writes in

Edition 2024

Keeps the 2023 presence, enum, and packing defaults, adds control over what a file exposes to the files that import it, and starts enforcing the style guide's names.

  • Symbol visibility: export and local modifiers on messages and enums, and features.default_symbol_visibility to set the default. Out of the box, top-level types are importable and nested types are local to their file.
  • import option, for a file you import only to use the custom options it defines. Its types do not come into scope, and its descriptors are not needed at runtime.
  • Naming-style enforcement, features.enforce_naming_style, is on: fields in lower_snake_case, enum values in UPPER_SNAKE_CASE, messages, enums, and services in PascalCase, with the compiler rejecting anything else. A file with older names can set it to STYLE_LEGACY until the names are updated.
  • Presence, enums, and packing behave the same as in 2023, but Edition 2024 changes symbol visibility and some generated APIs. Nested types become local, so other files that refer to them stop compiling. Some generated code changes too, such as Go switching to the Opaque API and C++ string fields returning views. Each change has a feature that restores the 2023 behavior, such as option features.(pb.go).api_level = API_OPEN;, and the feature settings reference lists them.

Protobuf 36

Edition 2026

Tightens the 2024 defaults, and tidies the JSON mapping for enums.

  • Naming style tightens: a field can no longer be named so that it collides with a generated accessor, such as has_x, get_x, or clear_x, and no field can be named descriptor.
  • Symbol visibility defaults to strict, so a type is importable only when it is explicitly exported.
  • Enum values can carry a custom JSON name, just as fields do with json_name.

How the language got here

Protobuf has been public since 2008. These are the releases that changed the language, from proto2 through editions.

INTERNAL2001

Google Internal Launch

Protobuf was born inside Google to solve the overhead of XML. It became widely used for internal communication.

View Details
PROTO22008

Public Open Source

First public release, bringing the internal language's "required" fields and extension mechanism into the open.

View Details
PROTO32016

Proto3 Released

Removed "required" fields, standardized the JSON mapping, and dropped explicit presence for singular scalar fields.

View Details
EDITIONS2024

Editions Arrive

Moves the language from syntax "versions" to individually settable "features", so it can add behavior without waiting on a new syntax release. Three have shipped so far: 2023, 2024, and 2026.

View Details

Further Reading

Next

Standard Options

Protobuf options at the file, message, and field level: what the well-known options do and how options change generated code without changing the wire format.