Library / SDK
gookit/validate avatar
gookit/validate

Gookit validate: struct tags, filters and custom messages in one Go validation library

⚔ Go package for data validation and filtering. support Map, Struct, Form data. Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器,支持自定义验证器、自定义消息、字段翻译。

1,165 stars126 forksGoMIT

At a glance

What is it?
Gookit validate is a Go library for validating and filtering Map, Struct and request data, with more than seventy built-in validators, filter and convert rules that run before validation, and a scene system for validating different fields in different contexts. It suits Go services that want validation expressed in struct tags with custom error text, and the v2 line changed the module path and the cascade rules.
Who is it for?
Gookit validate fits a Go codebase that wants validation rules next to the struct definition, custom error messages in several languages, and the ability to clean incoming data before checking it, because the `validate` tag carries the rules and the `message` and `label` tags carry the human-facing text in one place.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Validation and filtering in the same pass

Gookit validate is described as a generic Go data validate and filter tool library, and the pairing of those two words is the first thing to understand about it.

Most validation libraries check values. This one also transforms them first. Support for filter, sanitize and convert data before validate is a listed feature, and the dependency list shows why it is cheap to implement: the library depends on `github.com/gookit/filter` for the filter side, so the built-in filters are a separate package that happens to be maintained by the same author.

That ordering matters in practice. A form arrives with a phone number that has spaces and dashes, an age that is a string because it came from HTML, a boolean that is on. Filtering converts them to the type the struct expects, and then validation runs against values that are already normalised. Doing that by hand means writing a conversion pass before every validation call, and remembering to do it.

The scope of the data it accepts is broad. It supports quick validate of `Map`, `Struct` and `Request` data, where the request case covers `Form`, `JSON`, `url.Values` and `UploadedFile`. Validating an `http.Request` automatically collects data based on the request `Content-Type` value, so the same call works for a JSON body and a form post.

It also handles slices of values without ceremony. Checking each child value in a slice is expressed with a wildcard in the rule name, for example `v.StringRule("tags.*", "required|string")`.

The library is designed to sit inside a framework rather than beside it. It can be used in any framework, such as Gin, Echo, Chi and more, and it can also be used with no framework at all, since a rule can be applied directly to a value with `validate.Val("[email protected]", "required|email")`.

Struct tags carry the rules, the labels and the messages

The primary interface is a struct tag, and the example form in the documentation is a good summary of what a real form type looks like:

go
type UserForm struct {
	Name     string    `validate:"required|min_len:7" message:"required:{field} is required" label:"User Name"`
	Email    string    `validate:"email" message:"email is invalid" label:"User Email"`
	Age      int       `validate:"required|int|min:1|max:99" message:"int:age must int|min:age min value is 1"`
	CreateAt int       `validate:"min:1"`
	Safe     int       `validate:"-"`
	UpdateAt time.Time `validate:"required" message:"update time is required"`
	Code     string    `validate:"customValidator"`
}

Three tags do three separate jobs. The `validate` tag holds the rules, separated by pipes. The `message` tag holds custom error text, and it can be keyed by rule name so that the same field has a different message for `required` than for `email`. The `label` tag provides the field translation, which is the human readable name used in the message.

Two details in the tag syntax are worth noting. The `{field}` placeholder in a message is substituted with the label, so `required:{field} is required` becomes `required:User Name is required`. And `validate:"-"` skips the field entirely, which is how you mark a field that exists in the struct but should not be validated.

Field mapping is also configurable, with support for configuration of field mapping through a structure tag, reading the value of the `json` tag by default. So a struct that is already shaped for JSON does not need a second tag just for validation.

The rules are read as a small language. `required|min_len:7` means the value must be present and at least seven characters long. `required|int|min:1|max:99` chains a type check with range checks. The documentation counts more than seventy commonly used validators as built in, and the repository splits the source across files named for their domain, with `validators_string.go`, `validators_compare.go`, `validators_type.go` and `validators.go` at the top level.

Three interfaces let a struct own its own rules

Beyond tags, a struct can implement three methods, and the documentation describes them as convenient ways to do some customization.

`ConfigValidation(v *Validation)` is called after the validator instance is created, which is where you define validation scenes. `Messages() map[string]string` customizes the validator error messages, and `Translates() map[string]string` customizes field translations.

The scenes example is the interesting one:

go
func (f UserForm) ConfigValidation(v *validate.Validation) {
	v.WithScenes(validate.SValues{
		"add":    []string{"ExtInfo.Homepage", "Name", "Code"},
		"update": []string{"ExtInfo.CityName", "Name"},
	})
}

A create request and an update request validate different fields. On add you require the homepage, the name and the code; on update you require the city name and the name. The same struct serves both endpoints and each gets the rules it should, which is the situation where a single `validate` tag cannot express what you need and you would otherwise end up with two structs or a hand-rolled switch.

The messages map shows the keying scheme, which mixes rule names and field qualified rule names:

go
func (f UserForm) Messages() map[string]string {
	return validate.MS{
		"required": "oh! the {field} is required",
		"email": "email is invalid",
		"Name.required": "message for special field",
		"Age.int": "age must int",
		"Age.min": "age min value is 1",
	}
}

A bare rule key like `required` applies everywhere, and a qualified key like `Age.min` overrides it for one field and one rule. The precedence order between the two is what makes the qualified form useful.

There is also a custom validator mechanism, where a rule name in the tag maps to a method on the struct. The example uses `validate:"customValidator"` with a method that checks the length is four.

Sub-struct cascade is opt-in, and embedded structs are exempt

The rule for descending into nested structs is the one that surprises people, so it is worth reading precisely.

By default, with `CheckSubOnParentMarked=true`, a named sub-struct field, which can be a `struct`, a `*struct`, a slice of struct or a map of struct, is descended into to collect its inner rules only when it carries a `validate` tag. The tag value may be empty, so `validate:""` is enough to mark the field. A named field with no `validate` tag at all is not descended into.

The documentation describes this as the Java `@Valid` style of on-demand cascade, and it explicitly frames the benefit as avoiding pointless recursion through the whole struct tree.

That is a real difference from a library that walks every nested struct unconditionally. With opt-in cascade, a large struct with a few nested value objects does not pay for checking the ones you never meant to check, and you never get a surprise error about a field in a nested struct you had forgotten about.

The exemption is anonymous embedded structs. A type declared as `type Bar struct { Foo }`, where the field is promoted and is part of the parent, always cascades, and no tag is needed. The reasoning is that an embedded field is not a separate value with its own rules, it is the parent wearing a different hat, so treating it as a sub-struct would be wrong.

The `validate:"required"` on the `ExtInfo` field in the example is therefore not a rule about the nested struct's contents but a marker plus a requirement, and the marker is what lets the inner `Homepage` field's own rule run at all.

Reading the result, and the messages you can localise

Creating and running a validation is short. `validate.Struct(ptr)` creates a validation instance, and then `v.Validate()` runs the rules:

go
v := validate.Struct(u)

if v.Validate() { // validate ok
	// do something ...
} else {
	fmt.Println(v.Errors) // all error messages
	fmt.Println(v.Errors.One()) // returns a random error message text
	fmt.Println(v.Errors.OneError()) // returns a random error
	fmt.Println(v.Errors.Field("Name")) // returns error messages of the field
}

The `Errors` object has a few shapes of access, and the two `One` methods are the ones to read carefully. `One()` returns a random error message text and `OneError()` returns a random error, and the word random is doing deliberate work. When a form has five validation failures, showing one at random is not great UX, and the documentation does not present this as the recommended path. `Errors` for the full list and `Errors.Field("Name")` for one field are the useful accessors.

Internationalisation is built in rather than bolted on. The feature list mentions customizable i18n aware error messages, with `en`, `zh-CN` and `zh-TW` built in. The repository has a `locales/` directory for the message catalogues and a `messages.go` at the top level, and the `Messages()` interface is the per-struct override that sits above the built-in catalogues.

So there are three levels of message customisation: the built-in locale files, the per-struct `Messages()` map for your own wording, and the per-field `message` tag for the exceptions. The `label` tag feeds the `{field}` placeholder, and `Translates()` overrides the field names the same way `Messages()` overrides the text.

On the module itself, the v2 line moved the path to `github.com/gookit/validate/v2` and requires Go 1.21 or newer, with an upgrade guide for migrating from v1.x and a benchmark document comparing v1.5.7, v1.6.0 and v2.0.0. The go.mod requires `github.com/gookit/filter` and `github.com/gookit/goutil`, and nothing else beyond the standard indirect dependencies.

Where tag-driven validation stops working

The tag approach is genuinely good for form validation, and it has a shape that produces specific limits.

The first limit is that the rules are compile-time. A rule is a string in a struct tag, so changing a rule means changing code and rebuilding. If your business rules change often, live in a database, or are set by an administrator, you cannot express that with tags. The scene mechanism helps, because a scene is a named subset of the rules selected at runtime, but the rules inside a scene are still fixed at compile time. A library built around a schema, where the schema is data rather than code, is the tool for the other case.

The second limit is that the tag vocabulary is a closed set, plus whatever you add. There are more than seventy built-in validators, and the string rule syntax is compact enough that not every constraint you might want fits in it. A complex conditional rule often ends up as a custom validator method, which is fine, but it is a step beyond the elegance of the tag and it does not compose with the message and label machinery the way the built-in rules do.

The third limit is that the error messages, however customisable, are still strings tied to fields and rules. If your API needs structured errors with codes that a client can branch on, you will be parsing or mapping the message strings yourself, because the library models an error as a message.

The alternative is another tag-driven validation library, which is a fair comparison point. The differences that this documentation points to are the filtering step before validation, the scene system, the per-struct method hooks, and the built-in localisation. Against a hand rolled validation function, the tag approach wins when the rules are static and there are many fields, and loses when the logic is conditional and rule-heavy, because then the conditional logic has to be pushed into custom validators anyway and you have written the validation by hand with extra steps.

The practical test is whether your form types are mostly independent field rules, which is the case this library is designed for, or whether they are mostly interdependent business rules, which it is not.

Editorial conclusion

Gookit validate fits a Go codebase that wants validation rules next to the struct definition, custom error messages in several languages, and the ability to clean incoming data before checking it, because the `validate` tag carries the rules and the `message` and `label` tags carry the human-facing text in one place. Skip it if you want schema based validation with code generation, since this is a runtime library driven by tags, and skip it if your rules change at runtime, because the tag-driven model is compile-time by nature. Before you upgrade from v1, read the upgrade guide, because v2 changed the module path to `github.com/gookit/validate/v2` and requires Go 1.21 or newer. The last push was on 2026-10-19 and the current release is v2.0.2 from 2026-08-18.

Frequently asked questions

How do I install Gookit validate?

Run `go get github.com/gookit/validate/v2`. The v2 line requires Go 1.21 or newer, and there is an upgrade guide in docs/UPGRADE-v2.md for migrating from v1.x.

How do I define validation rules on a Go struct?

With the `validate` struct tag, using pipe separated rules such as `validate:"required|min_len:7"`. The `message` tag customizes the error text per rule and the `label` tag sets the field name used in messages.

What are validation scenes in Gookit validate?

A scene names a subset of fields to verify, so different fields can be checked in different contexts. A struct defines them by implementing `ConfigValidation(v *Validation)` and calling `v.WithScenes`.

When does Gookit validate descend into a nested struct?

A named sub-struct field is descended into only when it carries a `validate` tag, and the tag value may be empty. Anonymous embedded structs always cascade without a tag.

Does Gookit validate filter data before validating it?

Yes. Filter, sanitize and convert steps run before validation, using the separate `github.com/gookit/filter` package for the built-in filters, so values are normalised before the rules are applied.

Official sources

  1. gookit/validate on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gookit-validate.svg)](https://hysenlabs.com/projects/gookit-validate)

Was this useful?