Truthful validation for Go — Collect all errors in one pass. Beautiful, JSON-serializable errors. Perfect for APIs. Powered by xrr.
Go validation libraries often force you to choose between:
- Stopping at the first error (bad UX for users)
- Returning ugly, hard-to-parse errors
verax solves both: it validates everything in a single pass, collects * all* failures, and returns clean, structured errors that are ready to serialize to JSON and send straight to your API clients.
Built on top of xrr — so you get stable error
codes, rich metadata, and full compatibility with errors.Is/As, slog, and
more.
- All-errors-in-one-pass —
ValidateStructreports every field failure - Production-ready JSON — Errors implement
json.Marshalerperfectly for API responses - Simple, fluent API —
Validate,ValidateStruct,Field, and rich rules - Built-in rules galore —
Required,Min/Max,Length,Match,In,Each,Map, network rules, semver, and more - Struct tags — Uses
jsontag by default (customizable) - Self-validating structs — Implement
verax.Validator - Complex data — First-class support for slices, arrays, and maps (with indexed/key errors)
- Fully extensible — Custom rules via
Ruleinterface, reusableSets, orByfunc - Conditional logic —
When,Skip, and per-rule conditions - Error classification —
IsValidationError,IsInternalError,IsVeraxError - Serializable rules — Most built-in rules implement
SpecableRule; encode to JSON and rebuild at runtime viaspec.Registry - xrr-powered — Stable codes + structured metadata out of the box
go get github.com/ctx42/veraxreq := &CreateUserRequest{
Name: "A",
Email: "bad-email",
Age: 15,
}
err := req.Validate()
PrintError(err)
PrintJSON(err)
// Output:
// ERROR:
//
// - age: must be greater or equal to 18
// - email: must be in a valid format value
// - name: the length must be between 2 and 50
//
// JSON:
// {
// "age": {
// "code": "ECInvRange",
// "error": "must be greater or equal to 18"
// },
// "email": {
// "code": "ECInvMatch",
// "error": "must be in a valid format value"
// },
// "name": {
// "code": "ECInvLength",
// "error": "the length must be between 2 and 50"
// }
// }- Installation
- Validating Primitive Values
- Struct Validation
- Slices, Arrays & Maps
- Custom Rules
- Conditional & Skipping Rules
- Working with Errors
- Rule Serialization
- Comparison with Other Libraries
- Contributing
- License
go get github.com/ctx42/veraxUse verax.Validate for single values. Stops at first failure.
err := verax.Validate(
45,
verax.Required,
verax.Min(42),
verax.Max(44),
)
PrintError(err)
PrintJSON(err)
// Output:
// ERROR:
//
// - must be less or equal to 44
//
// JSON:
// {
// "code": "ECInvRange",
// "error": "must be less or equal to 44"
// }verax.ValidateStruct collects all errors from every field.
err := verax.ValidateStruct(&myStruct,
verax.Field(&myStruct.Field1, rules...),
verax.Field(&myStruct.Field2, rules...),
)Supports custom struct tags, Validator interface, etc.
Full support with per-element and per-key error reporting using Each, Map,
Key, etc.
- Implement
verax.Rule - Reuse with
verax.Set - Quick functions with
verax.By
Fine-grained control with verax.When, verax.Skip, and method chaining.
All errors are xrr-powered:
- Stable codes (
ECInvRange, etc.) - JSON marshaling
- Classification helpers (
IsValidationError, etc.) - Easy inspection
Perfect for API responses and logging.
Most built-in rules implement verax.SpecableRule — they can be serialized to
JSON and reconstructed at runtime. This lets you store validation configuration
in a database or config file and rebuild rules on the fly.
Encoding a rule to JSON:
rule := verax.Min(18)
reg := spec.NewRegistry[verax.Rule]()
reg.RegisterBuilders(verax.Builders())
spc, _ := rule.Spec()
data, _ := reg.EncodeSpec(spc)
fmt.Println(string(data))
// Output:
// {"name":"range-rule","args":{"mode":{"type":"string","value":"min"},"value":{"type":"int","value":18}}}Decoding and rebuilding:
data := []byte(`{"name":"range-rule","args":{"mode":{"type":"string","value":"min"},"value":{"type":"int","value":18}}}`)
reg := spec.NewRegistry[verax.Rule]()
reg.RegisterBuilders(verax.Builders())
restored, _ := reg.DecodeAndBuild(data)
err := verax.Validate(15, restored)
fmt.Println(err)
// Output:
// must be greater or equal to 18Set rules are serializable too — their inner rules are encoded recursively:
ageRule := verax.Set{verax.Required, verax.Min(18), verax.Max(120)}
spc, _ := ageRule.Spec()
data, _ := reg.EncodeSpec(spc)By rule with a custom function:
By rules wrap a function reference. Register it as a named Source so it
can be resolved by name during decoding:
// Register the function as a named source so it survives serialization.
src, _ := spec.NewSource("nonEmptyWord", nonEmptyWord)
reg := spec.NewRegistry[verax.Rule]()
reg.RegisterBuilders(verax.Builders())
reg.RegisterSource(src)
// Encode.
rule := verax.By(nonEmptyWord)
spc, _ := rule.Spec()
data, _ := reg.EncodeSpec(spc)
// Decode and rebuild — the function is resolved by name from the registry.
restored, _ := reg.DecodeAndBuild(data)
err := verax.Validate("hi", restored)
fmt.Println(err)
// Output:
// must be at least 3 characters longSerializable built-ins: Absent, By, Contain, Each, Equal,
Fail, In, Length, Map, Match, Noop, Required, Skip,
Min/Max (via Range), and Set.
TypeRule is intentionally excluded: it holds a reflect.Type with no
portable cross-language representation.
| Feature | ozzo-validation | validator.v10 | verax |
|---|---|---|---|
| Collects all errors | Yes | No | Yes |
| JSON-serializable errors | Manual | Limited | Built-in |
| xrr integration (codes) | No | No | Yes |
| Slices/maps with details | Yes | Yes | Yes |
| Conditional rules | Yes | Yes | Yes |
| Serializable rules | No | No | Yes |
| Simple API | Good | Complex | Excellent |
go get github.com/ctx42/verax
Full API docs → pkg.go.dev/github.com/ctx42/verax
Changelog → CHANGELOG.md