Getting Started with Go Validation
Introduction
Section titled “Introduction”Valgo is a type-safe, expressive, and extensible validation library for Go with built-in i18n support.
Unlike validation libraries that rely on struct tags, Valgo defines validation rules as functions. This gives you greater flexibility to validate any value, compose rules programmatically, and decide where validation belongs within your application.
Valgo can be customized to fit your application’s needs, from overriding validation messages to localizing them for different languages and contexts.
Install
Section titled “Install”go get github.com/cohesivestack/valgoValgo v0.9.0 has been tested with Go 1.23 and later.
Agent skill
Section titled “Agent skill”This repository includes a Valgo Agent Skill installable with npx skills:
npx skills add cohesivestack/valgo --skill valgoYour first validation
Section titled “Your first validation”Is(...) creates a validation session. Within each validator chain, rules are
evaluated from left to right and stop after the first failed rule. OR groups and
OrElse() have their own control-flow behavior; see Validators -> OR Operators.
import ( "encoding/json" "fmt" v "github.com/cohesivestack/valgo")
val := v.Is( v.String("Bob", "full_name").Not().Blank().LengthBetween(4, 20), v.Number(17, "age").GreaterThan(18),)
if err := val.ToError(); err != nil { out, _ := json.MarshalIndent(err, "", " ") fmt.Println(string(out))}Extended example
Section titled “Extended example”This larger example composes validation without struct tags. It shows alternative rules, optional values, validation that depends on earlier results, and nested error paths in one flow.
type Address struct { Line1 string City string Country string PostalCode string}
type Registration struct { Name string Email string Password string PasswordConfirmation string ContactPreference string ReferralCode string Address Address}
func validateRegistration(input Registration) *v.Validation { return v.Is( v.String(input.Name, "name").Not().Blank().LengthBetween(2, 80), v.String(input.Email, "email").Not().Blank(), v.String(input.Password, "password").Not().Blank().LengthBetween(10, 64), v.String(input.ContactPreference, "contact_preference"). EqualTo("email").Or().EqualTo("sms"), v.String(input.ReferralCode, "referral_code"). Empty().OrElse().MatchingTo(regexp.MustCompile(`^[A-Z0-9]{6,12}$`)), ).IfPathValid( "password", v.Is(v.String(input.PasswordConfirmation, "password_confirmation"). EqualTo(input.Password)), ).In("address", v.Is( v.String(input.Address.Line1, "line1").Not().Blank(), v.String(input.Address.City, "city").Not().Blank(), v.String(input.Address.Country, "country").Not().Blank().EqualTo("US"), v.String(input.Address.PostalCode, "postal_code").Not().Blank(), ).WhenAllValid([]string{"country", "postal_code"}, func(val *v.Validation) { if err := verifyPostalCode("US", input.Address.PostalCode); err != nil { val.AddErrorMessage("postal_code", "Postal code could not be verified") } }), )}The example assumes that the application provides verifyPostalCode.
Or()acceptsemailorsmsas the contact preference.OrElse()accepts an empty referral code without evaluating the regex.IfPathValid()merges the password-confirmation validation only after the password passes.In("address", ...)prefixes nested errors withaddress.WhenAllValid()calls postal-code verification only after the initial country and postal-code rules pass.
See OR operators, conditional validation, and namespaces for the detailed behavior.
When to use Is vs Check
Section titled “When to use Is vs Check”Is(...): stops a validator chain after its first failed rule.Check(...): continues evaluating rules after failures so it can collect multiple messages. A successfulOrElse()still cuts the remainder of its chain by design.
val := v.Check( v.String("", "full_name").Not().Blank().LengthBetween(4, 20),)
_ = val.Valid() // false, with 2 messages for full_nameNested models and collections
Section titled “Nested models and collections”Use namespaces to build structured paths:
In("ns", ...)for nested structsInRow("list", i, ...)for slices of structsInCell("list", i, ...)for slices of scalar values
See Namespaces and Slices and indexed errors for complete examples.