Using Protovalidate standard rules
Once you've added Protovalidate to your project, you're ready to begin adding validation rules to your Protobuf files. On this page, you'll learn how rules are defined and explore the standard rules for scalar types, oneofs
, maps
, enums
, and even entire messages.
Code available
Companion code for this page is available in GitHub.
Protovalidate rules
Protovalidate rules are Protobuf option annotations representing validation rules and business requirements. When a message is validated, every rule must pass for it to be considered valid.
All Protovalidate rules are defined in the Common Expression Language (CEL). Protovalidate's built-in rules provide a shorthand syntax for dozens of common validation rules, and you're free to examine their CEL definitions within the API itself.
On this page, we'll explore Protovalidate's built-in rules, starting with simple scalar fields.
Field rules
Field rules are the simplest and most commonly used Protovalidate rules. A field rule applies one requirement to one field in a message.
For example, you can require that first_name
meet a minimum length:
Simple field rule
message User {
string first_name = 1 [
(buf.validate.field).string.min_len = 1
];
}
Multiple rules
You can combine field rules to express a complete set of requirements:
Multiple field rules
message User {
string first_name = 1 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 50
];
}
Scalar (simple) rules
Scalar fields — bool
, string
, bytes
, and numeric types — are the simplest to validate. Protovalidate's standard rules handle most common validation requirements, including regular expression matching.
Scalar rule examples
message User {
string name = 1 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 100
];
string email = 2 [(buf.validate.field).string.email = true];
bool verified = 3 [(buf.validate.field).bool.const = true];
bytes password = 4 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]*$"];
}
View reference documentation for each scalar type's rules:
- bool
- string
- bytes
- double, fixed32, fixed64, float, int32, int64, sfixed32, sfixed64, sint32, sint64, uint32, uint64
Enum rules
Protovalidate provides validation rules for enum
types, allowing you to validate that a message's value is within the defined values (defined_only
), in a set of values (in
), not within a set of values (not_in
), and more.
Enum rule example
message Order {
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_PENDING = 1;
STATUS_PROCESSING = 2;
STATUS_SHIPPED = 3;
STATUS_CANCELED = 4;
}
// `status` should only allow values within the Status enum.
Status status = 1 [
(buf.validate.field).enum.defined_only = true
];
}
View reference documentation for enum rules.
Repeated rules
Repeated fields can be validated for minimum and maximum length, uniqueness, and even have their contents validated. In this example, a repeated field must meet a minimum number of items, and each item must meet a set of string rules:
Repeated rule example
message RepeatedExample {
repeated string terms = 1 [
(buf.validate.field).repeated.min_items = 1,
(buf.validate.field).repeated.items = {
string: {
min_len: 5
max_len: 20
}
}
];
}
View reference documentation for repeated rules.
Map rules
Protovalidate's map
rules provide common map
validation tasks. You can validate minimum or maximum numbers of key-value pairs and even express sets of rules applied to keys and values.
In this example, each value in a map must meet a minimum and maximum length, and the map must have at least one key-value pair:
Map rule example
message MapExample {
map<string, string> terms = 1 [
(buf.validate.field).map.min_pairs = 1,
(buf.validate.field).map.values = {
string: {
min_len: 5
max_len: 20
}
}
];
}
View reference documentation for map rules.
Oneof rules
Protovalidate provides a single oneof
rule: required
. It states that exactly one field in the oneof
must be set. Note that fields within the oneof
may have their own rules applied.
In this example, a
or b
must be set, but any a
value must be non-empty:
Oneof rule example
message OneofExample {
oneof value {
// Exactly one of `a` or `b` must be set. If `a` is set, it must also be
// non-empty; whereas if `b` is set, it can still be an empty string.
option (buf.validate.oneof).required = true;
string a = 1 [(buf.validate.field).string.min_len = 1];
string b = 2;
}
}
As an alternative to the Protobuf oneof
construct, Protovalidate provides a message rule that ensures that only one of a set of fields is set.
View reference documentation for oneof rules.
Well-Known Type rules
Protovalidate provides standard rules for Protobuf's Well-Known Types such as Any
, Duration
, and Timestamp
:
Well-Known Type rules
message Event {
google.protobuf.Any data = 1 [
(buf.validate.field).any = {
in: ["type.googleapis.com/MyType1"]
}
];
google.protobuf.Duration duration = 2 [
(buf.validate.field).duration.gte = {
seconds: 1
},
(buf.validate.field).duration.lte = {
seconds: 3600
}
];
google.protobuf.Timestamp timestamp = 3 [
(buf.validate.field).timestamp.lte = {
seconds: 1735689600 // 2025-01-01T00:00:00Z
}
];
}
View reference documentation for each well-known type's rules:
How rules apply
If you add a rule to a field, validation does not necessarily apply.
Fields that do not track presence
Validation always applies to fields that don't track presence. Even when omitting values for these fields, each field will be validated. For example, all of the fields in this message are always validated, even if no value is provided:
Fields that are always validated in proto3
message User {
string email = 1 [(buf.validate.field).string.email = true];
bool verified = 2 [(buf.validate.field).bool.const = true];
repeated string terms = 3 [(buf.validate.field).repeated.min_items = 1];
map<string, int32> scores = 4 [(buf.validate.field).map.min_pairs = 1];
}
Fields that track presence
Fields that track presence behave differently; rules only apply if the field is set.
(In proto3
, only message fields, members of a Protobuf oneof
, and fields with the optional
label track presence.)
For a simple example, consider the following field with the optional
label:
Field that is only validated if set
message User {
optional string email = 1 [(buf.validate.field).string.email = true];
}
For this optional field, rules only apply if the field is set. Similarly, rules on fields in a oneof
only apply to the field that is set. Because message fields always track presence, rules only apply if a message is set.
To ensure that a field is set, you can use the required rule. It's typically used for message fields:
Require a field to be set
message Order {
User user = 1 [(buf.validate.field).required = true];
}
Note that field presence changes with proto2
, and with Editions. To learn which fields track presence, see the Field Presence cheat sheet.
Message rules
Message rules are validation constraints that apply to entire Protobuf messages. They're especially useful when you need to implement validation rules that need to work with multiple fields in CEL expressions. Message rules use the buf.validate.message
option, applied to the message itself.
disabled
The disabled
rule causes the entire message to be ignored by Protovalidate:
Disabling validation for a message
message DisableValidationMessage {
option (buf.validate.message).disabled = true;
}
View reference documentation for disabled.
cel
The cel
rule provides the foundation for custom validation rules capable of evaluating multiple fields within a message:
Message-level CEL rule example
message CelRuleExample {
// `first_name + last_name` cannot exceed 100 characters in length.
option (buf.validate.message).cel = {
id: "name.total.length",
message: "first_name and last_name, combined, cannot exceed 100 characters"
expression: "this.first_name.size() + this.last_name.size() < 100"
};
string first_name = 1;
string last_name = 2;
}
View reference documentation for cel.
oneof
The oneof
message rule provides a validation-based alternative to Protobuf's oneof
construct, allowing you to enforce that at most one (or exactly one) field from a specified set is populated without the code generation and compatibility limitations of traditional oneof
fields.
This rule is particularly useful when you need oneof
-like behavior but want to avoid the ergonomic issues with generated code, use oneof behavior with repeated or map fields, honor implicit presence, or need to maintain backwards compatibility in your schema evolution.
Configuration
fields
(repeated string): A list of field names that should be treated as mutually exclusive.required
(bool): Whentrue
, requires one field to be set. Whenfalse
(default), allows at most one field to be set.
Examples
Allow at most one field to be set:
import "buf/validate/validate.proto";
message UserRef {
option (buf.validate.message).oneof = { fields: ["id", "name"] };
string id = 1;
string name = 2;
}
Require one field to be set:
import "buf/validate/validate.proto";
message UserRef {
option (buf.validate.message).oneof = { fields: ["id", "name"], required: true };
string id = 1;
string name = 2;
}
View reference documentation for the oneof message rule. For rules that apply to oneof
fields, see oneof rules.
Nested messages
Protovalidate validates the entire message, including nested messages.
In the following example, Person
contains an Address
, each requiring all fields. Because no postal_code
is provided for the address
, the entire Person
message is invalid.
Ignoring
You can ignore nested messages by adding the ignore
rule using a value from the Ignore enum.
message Person {
string name = 1 [(buf.validate.field).required = true];
- Address address = 2 [(buf.validate.field).required = true];
+ Address address = 2 [
+ (buf.validate.field).ignore = IGNORE_ALWAYS
+ ];
}
View reference documentation for the ignore enum.
Next steps
- Learn to validate domain logic with CEL-based custom rules.