I started this library as an internal dependency for something else I was building (a Gin middleware library modeled on express-validator). What I needed was simple: functions that take a string and tell me whether it's a valid email, IBAN, MongoDB ID, whatever. What I found in the Go ecosystem didn't quite fit, so I started writing them myself.
That "small dependency" grew teeth. Today it's 89 validators and a sanitizer subpackage, and stable enough that I'm tagging v1.0.0 and putting it out there properly. If you've ever missed validator.js while writing Go, that's pretty much what this is.
Repo: github.com/bube054/validatorgo
Why I bothered
Go has solid validation libraries. I'm not knocking them. But most of them are built around structs and tags:
type User struct {
Email string `validate:"required,email"`
}
Which is fine for HTTP handlers. It's awkward when you've already got a string in your hand and just want to ask "is this a valid IBAN."
govalidator is closer in spirit but coverage is patchy and it hasn't really kept up. So you end up doing the regex archaeology dance: open Stack Overflow, find three different patterns for what you want, copy the one with the most upvotes, hope it's right. I got tired of that.
validator.js solved this for JavaScript years ago. So I copied the API shape and started porting things over.
Quick taste
import vgo "github.com/bube054/validatorgo"
vgo.IsEmail("user@example.com", nil)
vgo.IsMongoID("5f2a6c69e1d7a4e0077b4e6b")
vgo.IsIBAN("DE89370400440532013000", "DE")
vgo.IsCreditCard("4111111111111111", nil)
vgo.IsBTCAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
Each call returns (bool, error). That's the whole thing. No struct tags, no reflection, no setup.
What's actually in it
89 validators across the categories you tend to run into in real apps. Emails, URLs, IPs, FQDNs, MAC addresses. Every kind of ID I could think of: UUID, ULID, MongoID, ISIN, ISBN, ISSN, IMEI, SemVer, ISO 6346 shipping container codes. Country and locale codes (ISO 639, ISO 3166), postal codes, license plates, mobile numbers across 150+ locales, identity cards, passports, tax IDs, VAT numbers. IBANs across 70+ countries, BIC codes, credit cards, currency strings, BTC and ETH addresses. Hex, HSL, RGB colors. Dates and times in most common formats. Plus the basics: alpha, numeric, alphanumeric, JSON, Base32/58/64, hashes.
The sanitizer subpackage covers what you usually need around validation: trim, escape, whitelist/blacklist, NormalizeEmail (with provider-aware rules for Gmail, Outlook, Yahoo, iCloud), and converters like ToInt, ToFloat, ToBoolean, ToDate.
Full list and function signatures are in the README and on pkg.go.dev. I'm not going to dump the whole table here.
A few things I'm happy with
Options without ceremony. Most validators take an optional *Opts struct. Pass nil for defaults, or override only what you care about:
ok, _ := vgo.IsEmail("user@example.com", &vgo.IsEmailOpts{
RequireTld: vgo.Bool(false),
DomainSpecificValidation: true,
})
Helpers like vgo.Bool(), vgo.String(), vgo.Int() make pointer fields painless. Anything you leave as nil gets a sensible default.
Errors you can actually use. When validation fails you don't get a generic false. You get a structured error with a stable code:
ok, err := vgo.IsEmail("nope", nil)
if err != nil {
var ve *vgo.ValidationError
if errors.As(err, &ve) {
ve.Validator // "IsEmail"
ve.Code // "INVALID_FORMAT"
ve.Message // "invalid email format"
}
}
Codes are stable strings like INVALID_FORMAT, TOO_SHORT, MISSING_TLD, BLACKLISTED_HOST, INVALID_CHECKSUM, OUT_OF_RANGE. Easy to map to field-level errors in an API response.
Locale-aware where it matters. Mobile numbers, postal codes, identity cards, license plates, tax IDs all take a locale. Pass "any" if you don't care, or be specific:
vgo.IsMobilePhone("+2348012345678", []string{"en-NG"}, nil)
vgo.IsPostalCode("100001", "NG")
Why v1.0.0 and not v0.x.something
I wouldn't slap "1.0" on something I wasn't ready to support, so a few notes on what stable means here.
Test coverage sits around 87%. Every validator has a test suite, including the gnarly ones. The IBAN checksums and ISO 6346 container IDs took longer to get right than I'd like to admit.
The API isn't going to break under you. From here it's semver. No surprise rewrites of how options work or what errors look like.
It's already in production through ginvalidator, the Gin middleware that started all this. So it's been hit with real input from real users.
There are still gaps. More locales for postal codes. Stricter IBAN checksums for some countries. More passport formats. That's roadmap, not a blocker for shipping.
If this is useful to you
A star on the repo genuinely helps. It's the main signal I have for whether to keep investing time, and "discoverable on GitHub" is honestly half the battle for a library like this.
Forking it and trying it on something real is even better. If it breaks on input it shouldn't break on, that's an issue I want to see. Edge cases in the wild are how this gets sharper.
If you know other Gophers, sharing this with them matters more than it should. Reach in this corner of the ecosystem is hard, and word of mouth from one developer to another beats anything else.
And contributions are open. New locales, additional ISO standards, doc improvements — all welcome. If there's a validator you want and don't see, open an issue or a PR.
Links
- Repo: github.com/bube054/validatorgo
- Docs: pkg.go.dev/github.com/bube054/validatorgo
- Sister project (Gin middleware): ginvalidator
- The library that inspired it: validator.js If you build something with it, I'd genuinely like to hear about it. Drop a comment or tag me on GitHub.
— @bube054
Top comments (0)