The semantic validation library for Protobuf
Protobuf gives you type safety. Protovalidate gives you data correctness. Define validation rules directly on your schemas and enforce them identically in Go, JavaScript/TypeScript, Java, Python, and C++.
Type safety isn’t enough
Section titled “Type safety isn’t enough”This Protobuf schema is perfectly valid:
message User { string id = 1; string email = 2; string name = 3; int32 age = 4;}And so is this data:
{"id": "", "email": "not-an-email", "name": "", "age": -5}Every field has the right type. Every field has the wrong value. Without semantic validation, this data flows through your system unchecked — corrupting databases, breaking dashboards, propagating downstream.
Protovalidate fixes this
Section titled “Protovalidate fixes this”Add validation rules directly to your schema:
message User { // Must be a valid UUID string id = 1 [(buf.validate.field).string.uuid = true]; // Must be a valid email address string email = 2 [(buf.validate.field).string.email = true]; // Must not be empty, max 100 characters string name = 3 [ (buf.validate.field).string.min_len = 1, (buf.validate.field).string.max_len = 100 ]; // Must be between 0 and 150 int32 age = 4 [ (buf.validate.field).int32.gte = 0, (buf.validate.field).int32.lte = 150 ];}Now the garbage data from above is rejected at the boundary. The rules are self-documenting, machine-enforceable, and travel with the schema everywhere it goes.
Standard rules cover most of what you need
Section titled “Standard rules cover most of what you need”Protovalidate ships with built-in rules for the validations you write over and over again:
// String formatsstring email = 1 [(buf.validate.field).string.email = true];string uri = 2 [(buf.validate.field).string.uri = true];string country_code = 3 [(buf.validate.field).string.pattern = "^[A-Z]{2}$"];
// Numeric boundsint32 quantity = 4 [ (buf.validate.field).int32.gt = 0, (buf.validate.field).int32.lte = 10000];
// Required fieldsstring tenant_id = 5 [(buf.validate.field).required = true];
// Repeated field constraintsrepeated string tags = 6 [ (buf.validate.field).repeated.min_items = 1, (buf.validate.field).repeated.max_items = 10];
// Enum validation — reject unspecified valuesMyEnum status = 7 [(buf.validate.field).enum.defined_only = true];
// Duration rangesgoogle.protobuf.Duration timeout = 8 [ (buf.validate.field).duration.lte = {seconds: 3600}];String formats, numeric bounds, required fields, repeated constraints, enums, durations, timestamps, maps, and more — browse the full reference.
CEL handles the rest
Section titled “CEL handles the rest”For cross-field validation and complex business logic, Protovalidate supports CEL — a fast, safe expression language that evaluates identically across all supported languages.
Ensure a time range is valid:
message ScheduleEventRequest { google.protobuf.Timestamp start_time = 1 [(buf.validate.field).required = true]; google.protobuf.Timestamp end_time = 2 [(buf.validate.field).required = true];
// end_time must come after start_time option (buf.validate.message).cel = { id: "end_after_start" message: "end_time must be after start_time" expression: "this.end_time > this.start_time" };}Catch accidental data entry errors:
message AddContactRequest { string first_name = 1 [ (buf.validate.field).string.min_len = 1, (buf.validate.field).string.max_len = 50 ]; string last_name = 2 [ (buf.validate.field).string.min_len = 1, (buf.validate.field).string.max_len = 50 ]; string email_address = 3 [ (buf.validate.field).string.email = true ];
// Prevent name fields from matching the email address option (buf.validate.message).cel = { id: "name.not.email" message: "name fields cannot match email address" expression: "this.first_name != this.email_address && this.last_name != this.email_address" };}Standard rules plus CEL means there’s no validation you can’t express. See the custom rules guide for more.
One line of code to validate
Section titled “One line of code to validate”if err := protovalidate.Validate(message); err != nil { // Handle failure.}ValidationResult result = validator.validate(message);if (!result.isSuccess()) { // Handle failure.}try: protovalidate.validate(message)except protovalidate.ValidationError as e: # Handle failure.buf::validate::Violations results = validator.Validate(message).value();if (results.violations_size() > 0) { // Handle failure.}const validator = createValidator();const result = validator.validate(schema, message);if (result.kind !== "valid") { // Handle failure.}Define once, enforce everywhere. No per-language reimplementation. No drift between services.
Try it yourself
Section titled “Try it yourself”Play with rules, break things, see validation errors in real time — no setup required.
Get started
Section titled “Get started”- Pick your language: Go | TypeScript | Java | Python | C++
- Browse the standard rules reference
- Write custom rules with CEL
- Migrating from protoc-gen-validate? Migration guide