Buf currently provides a carefully curated set of lint rules designed to provide consistency and maintainability across a Protobuf schema of any size and any purpose, but without being so opinionated as to restrict organizations from making the design decisions they need to make for their individual APIs.
buf lint applies individual lint rules across your Protobuf schema, reporting
any violations as errors. All lint rules have an id, and belong to one or more
categories. On this page, we'll discuss the available categories, and the individual rules
within each category.
Although categories are not required to be tree form, as of now, they can be represented as such. Note this is just a human representation and is not actual configuration.
Buf will provide a more opinionated set of lint rules in an upcoming release under the
group for those organizations who want additional constraints enforced. These rules will
include items such as file and import ordering, naming, and type restrictions.
Buf is currently in beta. As such, we still may make minor edits to the lint categories,
and potentially add a few more rules. Once Buf is v1.0, however, no additional rules
will be added to any existing category except for
OTHER. We will not take a long amount of time
to hit v1.0, though - we expect to be at v1.0 within a few months.
Our Style Guide is here. This provides a concise document that
effectively includes all rules in the
DEFAULT category, as well as additional
recommendations that are not enforced. We provide this for ease of consumption across
your various teams, while linking back to this document for rationale for individual
Buf provides three "main top-level" categories of increasing strictness:
These provide the majority of lint rules you will want to apply.
Additionally, Buf provides "extra top-level" categories, currently:
These enforce additional constraints that users may want to apply to their Protobuf schema.
We will add
STRICT lint category in the near future. All user-requested rules will
go in a special category
MINIMAL category represents what we consider to be fundamental rules for modern Protobuf
development, regardless of style. We find these rules so important that if it were up to us (which
it is not), and
protoc could make breaking changes (which it can't, and shouldn't), these would be
required for protoc to produce valid output.
Of note, and since it is up to us, this category will be enforced for the future Buf Schema Registry.
Not applying these rules can lead to a myriad of bad situations across the variety of available
Protobuf plugins, especially plugins not built into
protoc itself. There is no downside to
applying these rules. If you can't tell, we highly recommend abiding by the
for your development sanity.
MINIMAL category includes three "sub-categories".
FILE_LAYOUT category includes three rules:
DIRECTORY_SAME_PACKAGEchecks that all files in a given directory are in the same package.
PACKAGE_SAME_DIRECTORYchecks that all files with a given package are in the same directory.
PACKAGE_DIRECTORY_MATCHchecks that all files with are in a directory that matches their package name.
In short, this verifies that all files that declare a given package
foo.bar.baz.v1 are in the
foo/bar/baz/v1 relative to root, and that only one such directory exists. For example,
assuming we have a single root
protoc doesn't enforce file structure in any way, however you will have a very bad time
with many Protobuf plugins across various languages if you do not do this. If specific
examples are needed, contact us and we'll work to add them.
This also has the effect of allowing imports to self-document their package, for example
you will know that the import
foo/bar/bat/v1/bat.proto has types in the package
There is no downside to maintaining this structure, and in fact many languages explicitly or effectively enforce such a file structure anyways (for example, Golang and Java).
Buf does not lint file option values, as explained in the what we left out section below. However, it is important to make sure that certain file option values are consistent across all files in a given Protobuf package if you do use them.
PACKAGE_AFFINITY category includes the following rules:
PACKAGE_SAME_CSHARP_NAMESPACEchecks that all files with a given package have the same value for the csharp_namespace option.
PACKAGE_SAME_GO_PACKAGEchecks that all files with a given package have the same value for the go_package option.
PACKAGE_SAME_JAVA_MULTIPLE_FILESchecks that all files with a given package have the same value for the java_multiple_files option.
PACKAGE_SAME_JAVA_PACKAGEchecks that all files with a given package have the same value for the java_package option.
PACKAGE_SAME_PHP_NAMESPACEchecks that all files with a given package have the same value for the php_namespace option.
PACKAGE_SAME_RUBY_PACKAGEchecks that all files with a given package have the same value for the ruby_package option.
PACKAGE_SAME_SWIFT_PREFIXchecks that all files with a given package have the same value for the swift_prefix option.
Each of these rules will also verify that if a given option is used in one file in a given package, it is used in every file.
For example, if we have file
foo_two.proto with package
foo.v1 must have these three options
set to the same value, and the other options unset:
SENSIBLE category outlaws certain Protobuf features that you should never use in modern
Protobuf development. It includes the following rules:
ENUM_NO_ALLOW_ALIASchecks that enums do not have the allow_alias option set.
FIELD_NO_DESCRIPTORchecks that field names are are not name capitalization of "descriptor" with any number of prefix or suffix underscores.
IMPORT_NO_PUBLICchecks that imports are not public.
IMPORT_NO_WEAKchecks that imports are not weak.
PACKAGE_DEFINEDchecks that all files with have a package defined.
This rule outlaws the following:
allow_alias option allows multiple enum values to have the same number. This can lead to
issues when working with the JSON representation of Protobuf, a first-class citizen of proto3.
If you get a serialized Protobuf value over the wire in binary format, it is unknown what
specific value in the enum it applies to, and JSON usually serialized enum values by name.
While in practice, if you declare an alias, you expect names to be interchangeable, this
can lead to hard-to-track bugs.
Instead of having an alias, we recommend deprecating your current enum, and making a new one with the enum value name you want. Or just stick with the current name for your enum value.
This rules outlaws field names being any capitalization of "descriptor", with any number of prefix or suffix underscores. For example:
This prevents a long-standing issue with Protobuf where certain languages generate an accessor named "descriptor" that conflicts with generated code for this field name. There is actually an option no_standard_descriptor_accessor on MessageOptions that allows mitigation of this issue for fields that are named "descriptor". As per the documentation there, developers should just avoid naming fields "descriptor". This actually happens more often than you may think.
These rules outlaw declaring imports as
weak. If you
didn't know this was possible, forget what you just learned in this sentence, and regardless
do not use these.
This rule requires all Protobuf files to specify a
package. It is possible to have
a Protobuf file that does not declare a package. If you did not know this was possible, forget
what you just learned, and regardless do not do this.
BASIC category includes everything from the
MINIMAL category, as well as the
category. That is, the following configuration:
Is equivalent to:
STYLE_BASIC category includes basic style checks that are widely accepted as standard Protobuf
style. These checks should generally be applied for all Protobuf schemas.
STYLE_BASIC category includes the following rules:
ENUM_PASCAL_CASEchecks that enums are PascalCase.
ENUM_VALUE_UPPER_SNAKE_CASEchecks that enum values are UPPER_SNAKE_CASE.
FIELD_LOWER_SNAKE_CASEchecks that field names are lower_snake_case.
MESSAGE_PASCAL_CASEchecks that messages are PascalCase.
ONEOF_LOWER_SNAKE_CASEchecks that oneof names are lower_snake_case.
PACKAGE_LOWER_SNAKE_CASEchecks that packages are lower_snake.case.
RPC_PASCAL_CASEchecks that RPCs are PascalCase.
SERVICE_PASCAL_CASEchecks that services are PascalCase.
DEFAULT category includes everything from the
BASIC category, as well as the
category. That is, the following configuration:
Is equivalent to:
As per it's name,
DEFAULT is also the default set of lint rules used by Buf if no
configuration is present, and represents the our baseline enforced recommendations for modern
Protobuf development without being overly burdensome.
STYLE_DEFAULT category includes everything in
STYLE_BASIC, as well as style checks
that we recommend for consistent, maintainable Protobuf schemas. We recommend applying all of
these checks to any schema you develop.
STYLE_DEFAULT category includes the following rules on top of
ENUM_VALUE_PREFIXchecks that enum values are prefixed with ENUM_NAME_UPPER_SNAKE_CASE.
ENUM_ZERO_VALUE_SUFFIXchecks that enum zero values are suffixed with _UNSPECIFIED (suffix is configurable).
FILE_LOWER_SNAKE_CASEchecks that filenames are lower_snake_case.
RPC_REQUEST_RESPONSE_UNIQUEchecks that RPCs request and response types are only used in one RPC (configurable).
RPC_REQUEST_STANDARD_NAMEchecks that RPC request type names are RPCNameRequest or ServiceNameRPCNameRequest (configurable).
RPC_RESPONSE_STANDARD_NAMEchecks that RPC response type names are RPCNameResponse or ServiceNameRPCNameResponse (configurable).
PACKAGE_VERSION_SUFFIXchecks that the last component of all packages is a version of the form v\d+, v\d+test.*, v\d+(alpha|beta)\d+, or v\d+p\d+(alpha|beta)\d+, where numbers are >=1.
SERVICE_SUFFIXchecks that services are suffixed with Service (suffix is configurable).
This rule requires that all enum value names are prefixed with the enum name. For example:
Protobuf enums use C++ scoping rules, which makes it not possible to have two enums in the same package with the same enum value name (an exception is when enums are nested, in which case this rule applies within the given message). While you may think that a given enum value name will always be unique across a package, APIs can develop over years, and there are countless examples of developers having to compromise on their enum names due to backwards compatibility issues. For example, you might have the following enum:
Two years later, you have an enum in the same package you want to add, but can't:
This rule requires that all enum values have a zero value of
The suffix is configurable.
All enums should have a zero value. Proto3 does not differentiate between set and unset fields, so if an enum field is not explicitly set, it defaults to the zero value. If an explicit zero value is not part of the enum definition, this will default to the actual zero value of the enum. For example, if you had:
scheme not explicitly set will default to
This rule says that all
.proto files must be named in
is the widely accepted standard.
These rules enforce the message name of RPC request/responses, and that all request/responses are unique.
One of the single most important rules to enforce in modern Protobuf development is to have a unique request and response message for every RPC. Separate RPCs should not have their request and response parameters controlled by the same Protobuf message, and if you share a Protobuf message between multiple RPCs, this results in multiple RPCs being affected when fields on this Protobuf message change. Even in simple cases, best practice is to always have a wrapper message for your RPC request and response types. Buf enforces this with these three rules by verifying the following:
- All request and response messages are unique across your Protobuf schema.
- All request and response messages are named after the RPC, either by naming them
For example, the following service definition abides by these rules:
There are configuration options associated with these three rules.
This rule enforces that the last component of a package must be a version
of the form
v\d+, v\d+test.*, v\d+(alpha|beta)\d+, or v\d+p\d+(alpha|beta)\d+, where numbers are >=1.
Examples (all taken directly from
One of the core promises of Protobuf API development is to never have breaking changes in your APIs, and Buf helps enforce this through the breaking change detector. However, there are scenarios where you do want to properly version your API. Instead of making changes, the proper method to do so is to make a completely new Protobuf package that is a copy of your existing Protobuf package, serve both packages server-side, and manually migrate your callers. This rule enforces that all packages have a version attached so that it is clear when a package represents a new version.
A common idiom is to use alpha and beta packages for packages that are still in development and can
have breaking changes. You can configure the breaking change detector to ignore breaking
changes in files for these packages with the
This rule enforces that all services are suffixed with
Service. For example:
Service names inherently end up having a lot of overlap with package names, and service naming often ends up inconsistent as a result across a larger Protobuf schema. Enforcing a consistent suffix takes away some of this inconsistency.
The suffix is configurable via the
lint.service_suffix option. For example, if
you have the following configuration in your
SERVICE_SUFFIX rule will enforce the following naming instead:
This is an "extra top-level" category that enforces that comments are present on various parts of your Protobuf schema.
COMMENTS category includes the following rules:
COMMENT_ENUMchecks that enums have non-empty comments.
COMMENT_ENUM_VALUEchecks that enum values have non-empty comments.
COMMENT_FIELDchecks that fields have non-empty comments.
COMMENT_MESSAGEchecks that messages have non-empty comments.
COMMENT_ONEOFchecks that oneof have non-empty comments.
COMMENT_RPCchecks that RPCs have non-empty comments.
COMMENT_SERVICEchecks that services have non-empty comments.
Note that only leading comments are considered - trailing comments do not count towards passing these rules.
Buf intends to host a documentation service (both public and on-prem) in the future, which may include a structured documentation scheme, however for now you may want to at least enforce that certain parts of your schema contain comments. This group allows such enforcement. Of note is that general usage may be not to use all rules in this category, instead selecting the rules you specifically want. For example:
This is an "extra top-level" category that outlaws streaming RPCs.
UNARY_RPC category includes the following rules:
RPC_NO_CLIENT_STREAMINGchecks that RPCs are not client streaming.
RPC_NO_SERVER_STREAMINGchecks that RPCs are not server streaming.
Some RPC protocols do not allow streaming RPCs, for example Twirp. This extra category enforces that no developer accidentally adds a streaming RPC if your setup does not support them. Additionally, streaming RPCs have a number of issues in general usage. See this discussion for more details.
This is an "extra top-level" category that includes lint rules not in a main collection.
This category can be modified between collection versions.
This rule enforces that the first enum value is the zero value.
This is a
proto3 requirement on build, but is not required in
proto2 on build. This rule
enforces that this is also followed in
As an example:
The above will result in generated code in certain languages defaulting to
SCHEME_FTP instead of
What we left out
We think that the above lint rules represent a set that will sufficiently enforce consistent
and maintainable Protobuf schemas, including for large organizations, without being so opinionated
as to not let your organization make it's own design decisions. We will add a
in the near future that goes further. Regardless, there are some potential rules we purposefully did not
write that deserve special mention.
File option values
Buf does not include linting for specific file option values. For example, there is no way to
enforce naming conventions on the
go_package file option, or to enforce that
It's not that we don't think consistency across these file option value is important - in fact, we think it's very important for easy Protobuf stub consumption. However, we're concentrated on the future, specifically our upcoming Buf Schema Registry, which aims to provide an easy mechanism to generate and consume Protobuf stubs across many languages. A core principle we feel strongly about is that language-specific file options shouldn't be part of your core Protobuf schema - your Protobuf schema should only describe language-independent elements as much as is possible.
The values for most file options, in fact, should be deduced in a stable and deterministic manner.
For example, we think that
java_package should likely be a constant prefix followed by the
package name as a suffix. Your
go_package should use the last component of your package name.
java_multiple_files should always be true. These aren't defaults, for backwards-compatibility
reasons, but if you're using a tool like Buf to produce your stubs, you shouldn't have to think
about any of this.
The Buf Schema Registry, therefore, will (optionally) automatically set all relevant file options that we would otherwise produce lint rules for, in a stable and deterministic manner. This will offload this concern completely from API developers, and we don't want to write a set of lint rules that we think shouldn't constrain your API development.
Buf does enforce, however, that specific file options are the same across a given package, done
PACKAGE_AFFINITY group as described above. We do find this to be important, regardless
of what values you choose, although we will allow you to override any such file options in the
future Buf Schema Registry.
Specific custom options
There are no lint rules for widely-used custom options such as google.api options or protoc-gen-validate. This is really just a matter of bandwidth - we recognize the usefulness of having lint rules for these options, and could add them if there's widespread demand, but we're still in beta, and there's a lot of thought that needs to go into issues such as forwards and backwards compatibility for these external options. Please contact us if this is a big need for your organization.
Buf stays away from enforcing naming opinions, such as package name restrictions (beyond versioning
lower_snake_case), or field naming such as
standardization. This is to provide maximum usefulness of the
DEFAULT category out of the box.
Some naming restrictions will be a part of the
STRICT category - we certainly have plenty of opinions on the
subject, and can will build this category to aim to help organizations maintain naming consistency
while aligning with existing various style guide such as the Google, Uber, and Google API guides.
Buf stays away from formatting issues. There's a few reasons for this:
- There's so many varying opinions about how to name your Protobuf files and where to place various types that trying to enforce just one right now doesn't seem valuable. Other languages don't generally enforce what file should contain what elements, and we're sticking with that for now.
- File ordering and structure doesn't generally affect generated code or serialized structure in any significant way, so the practical benefit or any such enforcement is low.
- Formatting is a nice to have, but is very difficult to achieve with a tool that is FileDescriptorSet-based, as FileDescriptorSets are lossy - even with source code info attached, FileDescriptorSets can drop detached comments, which means that a formatter may drop such comments. Any FileDescriptorSet-based tool claiming otherwise is hoping you don't hit these edge cases, effectively, and we don't want to use a third-party Protobuf parser, as all existing parsers have various issues. We rather be correct than close, and not lint items that should be a concern of a formatter, and don't feel like we can offer a formatter that reliably handles all Protobuf schemas at this time (and to our knowledge, no such formatter exists in the OSS world).
Adding or requesting new rules
If you'd like a new rule added, please contact us to discuss it. We'll add rules if we think they're maintainable and could have widespread value. Most rules can be very easily added, and although buf is OSS, it's usually easier for us to add it ourselves.