Style Guide

Provides direction for how best to structure your proto definitions.

This document provides a style guide for .proto files. By following these conventions, you’ll make your protocol buffer message definitions and their corresponding classes consistent and easy to read.

Standard File Formatting

  • Keep the line length to 80 characters.
  • Use an indent of 2 spaces.
  • Prefer the use of double quotes for strings.

File Structure

Files should be named lower_snake_case.proto.

All files should be ordered in the following manner:

  1. License header (if applicable)
  2. File overview
  3. Syntax
  4. Package
  5. Imports (sorted)
  6. File options
  7. Everything else

Identifier naming styles

Protobuf identifiers use one of the following naming styles:

  1. TitleCase
    • Contains uppercase letters, lowercase letters, and numbers
    • The initial character is an uppercase letter
    • The initial letter of each word is capitalized
  2. lower_snake_case
    • Contains lowercase letters, underscores, and numbers
    • Words are separated by a single underscore
  3. UPPER_SNAKE_CASE
    • Contains uppercase letters, underscores, and numbers
    • Words are separated by a single underscore
  4. camelCase
    • Contains uppercase letters, lowercase letters, and numbers
    • The initial character is an lowercase letter
    • The initial letter of each subsequent word is capitalized
    • Note: The style guide below does not use camelCase for any identifier in .proto files; the terminology is only clarified here since some language’s generated code may transform identifiers into this style.

In all cases, treat abbreviations as though they are single words: use GetDnsRequest rather than GetDNSRequest, dns_request rather than d_n_s_request.

Underscores in Identifiers

Don’t use underscores as the initial or final character of a name. Any underscore should always be followed by a letter (not a number or a second underscore).

The motivation for this rule is that each protobuf language implementation may convert identifiers into the local language style: a name of song_id in a .proto file may end up having accessors for the field which are capitalized as as SongId, songId or song_id depending on the language.

By using underscores only before letters, it avoids situations where names may be distinct in one style, but would collide after they are transformed into one of the other styles.

For example, both DNS2 and DNS_2 would both transform into TitleCase as Dns2. Allowing either of those names can be lead to painful situations when a message is used only in some languages where the generated code keeps the original UPPER_SNAKE_CASE style, becomes widely established, and then is only later used in a language where names are transformed to TitleCase where they collide.

When applied, this style rule means that you should use XYZ2 or XYZ_V2 rather than XYZ_2.

Packages

Use dot-delimited lower_snake_case names as package names.

Multi-word package names may be lower_snake_case or dot.delimited (dot-delimited package names are emitted as nested packages/namespaces in most languages).

Package names should attempt to be a short but unique name based on the project name. Package names should not be Java packages (com.x.y); instead use x.y as the package and use the java_package option as needed.

Message Names

Use TitleCase for message names.

message SongRequest {
}

Field Names

Use snake_case for field names, including extensions.

Use pluralized names for repeated fields.

string song_name = 1;
repeated Song songs = 2;

Oneof Names

Use lower_snake_case for oneof names.

oneof song_id {
  string song_human_readable_id = 1;
  int64 song_machine_id = 2;
}

Enums

Use TitleCase for enum type names.

Use UPPER_SNAKE_CASE for enum value names.

enum FooBar {
  FOO_BAR_UNSPECIFIED = 0;
  FOO_BAR_FIRST_VALUE = 1;
  FOO_BAR_SECOND_VALUE = 2;
}

The first listed value should be a zero value enum and have the suffix of either _UNSPECIFIED or _UNKNOWN. This value may be used as an unknown/default value and should be distinct from any of the semantic values you expect to be explicitly set. For more information on the unspecified enum value, see the Proto Best Practices page.

Enum Value Prefixing

Enum values are semantically considered to not be scoped by their containing enum name, so the same name in two sibling enums is not allowed. For example, the following would be rejected by protoc since the SET value defined in the two enums are considered to be in the same scope:

enum CollectionType {
  COLLECTION_TYPE_UNSPECIFIED = 0;
  SET = 1;
  MAP = 2;
  ARRAY = 3;
}

enum TennisVictoryType {
  TENNIS_VICTORY_TYPE_UNSPECIFIED = 0;
  GAME = 1;
  SET = 2;
  MATCH = 3;
}

Name collisions are a high risk when enums are defined at the top level of a file (not nested inside a message definition); in that case the siblings include enums defined in other files that set the same package, where protoc may not be able to detect the collision has occurred at code generation time.

To avoid these risks, it is strongly recommended to do one of:

  • Prefix every value with the enum name (converted to UPPER_SNAKE_CASE)
  • Nest the enum inside a containing message

Either option is enough to mitigate collision risks, but prefer top-level enums with prefixed values over creating a message simply to mitigate the issue. Since some languages don’t support an enum being defined inside a “struct” type, preferring prefixed values ensures a consistent approach across binding languages.

Services

Use TitleCase for service names and method names.

service FooService {
  rpc GetSomething(GetSomethingRequest) returns (GetSomethingResponse);
  rpc ListSomething(ListSomethingRequest) returns (ListSomethingResponse);
}

For more service-related guidance, see Create Unique Protos per Method and Don’t Include Primitive Types in a Top-level Request or Response Proto in the API Best Practices topic, and Define Messages in Separate Files in Proto Best Practices.

Things to Avoid

Required Fields

Required fields are a way to enforce that a given field must be set when parsing wire bytes, and otherwise refuse to parse the message. The required invariant is generally not enforced on messages constructed in memory. Required fields were removed in proto3.

While enforcement of required fields at the schema level is intuitively desirable, one of the primary design goals of protobuf is to support long term schema evolution. No matter how obviously required a given field seems to be today, there is a plausible future where the field should no longer be set (e.g. an int64 user_id may need to migrate to a UserId user_id in the future).

Especially in the case of middleware servers that may forward messages that they don’t really need to process, the semantics of required has proven too harmful for those long-term evolution goals, and so is now very strongly discouraged.

See Required is Strongly Deprecated.

Groups

Groups is an alternate syntax and wire format for nested messages. Groups are considered deprecated in proto2 and were removed from proto3. You should use a nested message definition and field of that type instead of using the group syntax.

See groups.