Validation
Introduction
Goravel provides several different approaches to validate your application's incoming data. It is most common to use the Validate method available on all incoming HTTP requests. Goravel includes a wide variety of convenient validation rules.
Validation Quickstart
Let's take a closer look at a complete example of how to validate a form and return error messages to the user. This overview will provide you with a general understanding of how to validate incoming request data using Goravel.
Defining The Routes
First, let's assume we have the following routes defined in our routes/web.go file:
import "goravel/app/http/controllers"
postController := controllers.NewPostController()
facades.Route().Get("/post/create", postController.Create)
facades.Route().Post("/post", postController.Store)The GET route displays a form for creating a new blog post. The POST route stores the new post in the database.
Creating The Controller
Next, let's take a look at a simple controller that handles incoming requests to these routes. We'll leave the Store method empty for now:
package controllers
import (
"github.com/goravel/framework/contracts/http"
)
type PostController struct {}
func NewPostController() *PostController {
return &PostController{}
}
func (r *PostController) Store(ctx http.Context) {}Writing The Validation Logic
Now we are ready to fill in our Store method with the logic to validate the new blog post.
func (r *PostController) Store(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]any{
"title": "required|max:255",
"body": "required",
"code": "required|regex:^[0-9]{4,6}$",
})
}Nested Attributes
If the incoming HTTP request contains "nested" field data, you may specify these fields in your validation rules using the "dot" syntax:
validator, err := ctx.Request().Validate(map[string]any{
"title": "required|max:255",
"author.name": "required",
"author.description": "required",
})Slice Validation
If the incoming HTTP request contains "array" field data, you may specify these fields in your validation rules using the * syntax:
validator, err := ctx.Request().Validate(map[string]any{
"tags.*": "required",
})Wildcard rules preserve the original slice shape in validated data:
validator, err := facades.Validation().Make(ctx,
map[string]any{"scores": []int{1, 2}},
map[string]any{"scores.*": "required|integer"},
)
scores := validator.Validated()["scores"].([]int) // []int{1, 2}Form Request Validation
Creating Form Requests
For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation and authorization logic. To create a form request class, you may use the make:request Artisan CLI command:
./artisan make:request StorePostRequest
./artisan make:request user/StorePostRequestThe generated form request class will be placed in the app/http/requests directory. If this directory does not exist, it will be created when you run the make:request command. Each form request generated by Goravel has Authorize and Rules methods. You can also customize the optional Filters, Messages, Attributes, and PrepareForValidation methods for further operations.
The Authorize method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the Rules method returns the validation rules that should apply to the request's data:
package requests
import (
"mime/multipart"
"github.com/goravel/framework/contracts/http"
)
type StorePostRequest struct {
Name string `form:"name" json:"name"`
File *multipart.FileHeader `form:"file" json:"file"`
Files []*multipart.FileHeader `form:"files" json:"files"`
}
func (r *StorePostRequest) Authorize(ctx http.Context) error {
return nil
}
func (r *StorePostRequest) Rules(ctx http.Context) map[string]any {
return map[string]any{
// The keys are consistent with the incoming keys.
"name": "required|max:255",
"file": "required|file",
"files": "required|array",
"files.*": "required|file",
}
}Then you can use the ValidateRequest method to validate the request in the controller:
func (r *PostController) Store(ctx http.Context) {
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
}Check more rules in the Available Validation Rules section.
Note that since
formpassed values are ofstringtype by default, all fields in request should also be ofstringtype, otherwise please useJSONto pass values.
Authorizing Form Requests
The form request class also contains an Authorize method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your authorization gates and policies within this method:
func (r *StorePostRequest) Authorize(ctx http.Context) error {
var comment models.Comment
facades.Orm().Query().First(&comment)
if comment.ID == 0 {
return errors.New("no comment is found")
}
if !facades.Gate().Allows("update", map[string]any{
"comment": comment,
}) {
return errors.New("can't update comment")
}
return nil
}error will be passed to the return value of ctx.Request().ValidateRequest.
Filter Input Data
You can format the input data by improving the Filters method of the form request. This method should return a map of attribute/filter pairs. Filter values may be strings or []string values:
func (r *StorePostRequest) Filters(ctx http.Context) map[string]any {
return map[string]any{
"name": "trim",
"age": []string{"trim", "to_int"},
}
}Customizing The Error Messages
You may customize the error messages used by the form request by overriding the Messages method. This method should return an array of attribute / rule pairs and their corresponding error messages:
func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
return map[string]string{
"title.required": "A title is required",
"body.required": "A message is required",
}
}Customizing The Validation Attributes
Many of Goravel's built-in validation rule error messages contain an :attribute placeholder. If you would like the :attribute placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the Attributes method. This method should return an array of attribute / name pairs:
func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
return map[string]string{
"email": "email address",
}
}Preparing Input For Validation
If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the PrepareForValidation method:
func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
if name, exist := data.Get("name"); exist {
return data.Set("name", name.(string)+"1")
}
return nil
}Manually Creating Validators
If you do not want to use the Validate method on the request, you may create a validator instance manually using facades.Validation(). The Make method of the facade generates a new validator instance:
func (r *PostController) Store(ctx http.Context) http.Response {
validator, _ := facades.Validation().Make(
ctx,
map[string]any{
"name": "Goravel",
},
map[string]any{
"title": "required|max:255",
"body": "required",
})
if validator.Fails() {
// Return fail
}
var user models.User
err := validator.Bind(&user)
...
}After ctx, the data argument passed to the Make method can be map[string]any, struct, url.Values, map[string][]string, *http.Request, or another supported request data source. The next argument is a map[string]any of validation rules. Rule values may be strings or []string values.
Customizing The Error Messages
If needed, you may provide custom error messages that a validator instance should use instead of the default error messages provided by Goravel. You may pass custom messages with validation.Messages (also applicable to ctx.Request().Validate()):
validator, err := facades.Validation().Make(ctx, input, rules, validation.Messages(map[string]string{
"required": "The :attribute field is required.",
}))Specifying A Custom Message For A Given Attribute
Sometimes you may wish to specify a custom error message only for a specific attribute. You may do so using "dot" notation. Specify the attribute's name first, followed by the rule (also applicable to ctx.Request().Validate()):
validator, err := facades.Validation().Make(ctx, input, rules, validation.Messages(map[string]string{
"email.required": "We need to know your email address!",
}))Explicit message overrides have priority over custom rule defaults. Goravel resolves messages in this order: field.rule message, then rule message, then the custom rule's Message() return value.
Specifying Custom Attribute Values
Many of Goravel's built-in error messages include an :attribute placeholder that is replaced with the name of the field or attribute under validation. To customize the values used to replace these placeholders for specific fields, you may pass custom attributes with validation.Attributes (also applicable to ctx.Request().Validate()):
validator, err := facades.Validation().Make(ctx, input, rules, validation.Attributes(map[string]string{
"email": "email address",
}))Format Data Before Validation
You can format the data before validating the data for more flexible data validation, and you can pass the formatting callback with validation.PrepareForValidation (also applicable to ctx.Request().Validate()):
import (
"context"
validationcontract "github.com/goravel/framework/contracts/validation"
"github.com/goravel/framework/validation"
)
func (r *PostController) Store(ctx http.Context) http.Response {
validator, err := facades.Validation().Make(ctx, input, rules,
validation.PrepareForValidation(func(ctx context.Context, data validationcontract.Data) error {
if name, exist := data.Get("name"); exist {
return data.Set("name", name)
}
return nil
}))
...
}Working With Validated Input
After validating incoming request data using form requests or manually created validator instances, you can retrieve the validated data or bind the request data to a struct.
- After validation passes, use the
Validatedmethod to retrieve only fields covered by validation rules. Excluded fields are omitted, and wildcard slice rules keep the original slice shape:
rules := map[string]any{
"name": "required|string",
"scores.*": "required|integer",
}
validator, err := ctx.Request().Validate(rules)
validated := validator.Validated()- Use the
Bindmethod to bind data to a struct after validation succeeds.Bindmerges validated data back over the original request data, so fields without rules can still be bound:
validator, err := ctx.Request().Validate(rules)
var user models.User
err := validator.Bind(&user)
validator, err := facades.Validation().Make(ctx, input, rules)
var user models.User
err := validator.Bind(&user)- The incoming data is automatically bound to the form request when you use request validation:
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
fmt.Println(storePost.Name)Working With Error Messages
Retrieving One Error Message For A Field
validator, err := ctx.Request().Validate(rules)
validator, err := facades.Validation().Make(ctx, input, rules)
message := validator.Errors().One("email")Retrieving All Error Messages For A Field
messages := validator.Errors().Get("email")Retrieving All Error Messages For All Fields
messages := validator.Errors().All()Determining If Error Messages Exist For A Field
if validator.Errors().Has("email") {
//
}Translation & Localization
Using Translation for Validation Messages
Validation error messages are now powered by the framework's localization system. Default messages are embedded in the framework and automatically use the application's current locale. The message lookup follows this order:
- Form-request
Messages()or explicitvalidation.Messages()overrides (field.rule, thenrule) - Custom rule's
Message()method - Translation file lookup with
validation.*keys (e.g.,validation.required,validation.min.string) - Embedded default messages
For size-based rules like min, max, between, size, gt, gte, lt, and lte, the translation key includes the attribute type suffix (e.g., min.string, min.numeric, min.array, min.file).
Publishing Validation Language Files
Use the lang:publish Artisan command to publish the validation language files to your application's lang directory:
go run . artisan lang:publishThis copies the validation translation files from the framework into lang/en/validation.json, where you can customize the default error messages.
To overwrite existing files, use the --force (or -f) flag:
go run . artisan lang:publish --forceCustomizing Default Messages
After publishing, edit lang/en/validation.json to customize any validation message. For example:
{
"required": "You forgot the :attribute field!",
"email": "The :attribute must be a valid email address."
}These custom messages will be used by all validators in your application, unless overridden by form request Messages() or explicit validation.Messages().
Translating to Other Languages
To support additional languages, create a copy of the published lang/en/validation.json in the corresponding language directory (e.g., lang/zh/validation.json) and translate the messages. The framework will use the appropriate language file based on the current locale set by facades.App().SetLocale(ctx, "zh").
Available Validation Rules
Validation rule names now use snake_case by default. Rules and filters are defined with map[string]any; each value must be either a pipe-separated string or a []string of rule names.
rules := map[string]any{
"title": "required|string|max:255",
"slug": []string{"required", "regex:^(news|docs)-[a-z]+$", "string"},
}Use []string when a regex or not_regex pattern contains | and you need to add more rules after it.
| Category | Rules |
|---|---|
| Required | required, required_if, required_unless, required_with, required_with_all, required_without, required_without_all, required_if_accepted, required_if_declined |
| Presence | filled, present, present_if, present_unless, present_with, present_with_all, missing, missing_if, missing_unless, missing_with, missing_with_all |
| Accepted / Declined | accepted, accepted_if, declined, declined_if |
| Prohibited | prohibited, prohibited_if, prohibited_unless, prohibited_if_accepted, prohibited_if_declined, prohibits |
| Type | string, integer, int, uint, numeric, boolean, bool, float, array, list, slice, map |
| Size | size, min, max, between, gt, gte, lt, lte |
| Numeric | digits, digits_between, decimal, multiple_of, min_digits, max_digits |
| String Format | alpha, alpha_num, alpha_dash, ascii, email, url, active_url, ip, ipv4, ipv6, mac_address, mac, json, uuid, uuid3, uuid4, uuid5, ulid, hex_color, regex, not_regex, lowercase, uppercase |
| String Content | starts_with, doesnt_start_with, ends_with, doesnt_end_with, contains, doesnt_contain, confirmed |
| Comparison | same, different, eq, ne, in, not_in, in_array, in_array_keys |
| Date | date, date_format, date_equals, before, before_or_equal, after, after_or_equal, timezone |
| Exclusion | exclude, exclude_if, exclude_unless, exclude_with, exclude_without |
| File | file, image, mimes, mimetypes, extensions, dimensions, encoding |
| Control | bail, nullable, sometimes |
| Array / Database | distinct, required_array_keys, exists, unique |
The size, min, max, between, gt, gte, lt, and lte rules are type-aware. They compare numeric values for numeric fields, string length for strings, element count for arrays, slices, and maps, and file size for files.
The exists rule uses exists:table,column1,column2,.... The unique rule uses unique:table,column,idColumn,except1,except2,.... Both rules support connection.table syntax for the table parameter.
The active_url rule performs a DNS lookup for the URL host. Use it carefully on request hot paths because DNS resolution can add latency.
Deprecated Rule Aliases
The following aliases remain backward-compatible, but will be removed in the next major version. Prefer the new snake_case names:
| Deprecated | Use Instead |
|---|---|
len | size |
min_len | min |
max_len | max |
eq_field | same |
ne_field | different |
gt_field | gt |
gte_field | gte |
lt_field | lt |
lte_field | lte |
gt_date | after |
lt_date | before |
gte_date | after_or_equal |
lte_date | before_or_equal |
number | numeric |
full_url | url |
Custom Validation Rules
Goravel provides a variety of helpful validation rules; however, you may wish to specify some of your own. One method of registering custom validation rules is using rule objects. To generate a new rule object, you can simply use the make:rule Artisan command.
Creating Custom Rules
For instance, if you want to verify that a string is uppercase, you can create a rule with this command. Goravel will then save this new rule in the app/rules directory. If this directory does not exist, Goravel will create it when you run the Artisan command to create your rule.
./artisan make:rule Uppercase
./artisan make:rule user/UppercaseDefining Custom Rules
After creating the rule, we need to define its behavior. A rule object has two methods: Passes and Message. The Passes method receives all data, including the data to be validated and the validation parameters. It should return true or false depending on whether the attribute value is valid. The Message method should return the error message for validation that should be used when the validation fails.
package rules
import (
"context"
"strings"
"github.com/goravel/framework/contracts/validation"
)
type Uppercase struct {
}
// Signature The name of the rule.
func (receiver *Uppercase) Signature() string {
return "uppercase"
}
// Passes Determine if the validation rule passes.
func (receiver *Uppercase) Passes(ctx context.Context, data validation.Data, val any, options ...any) bool {
return strings.ToUpper(val.(string)) == val.(string)
}
// Message Get the validation error message.
func (receiver *Uppercase) Message(ctx context.Context) string {
return "The :attribute must be uppercase."
}Custom rule messages support the following placeholders:
| Placeholder | Description |
|---|---|
:attribute | The field name or custom attribute |
:value | The field value being validated |
:option0, :option1, ... | Rule parameters passed to Passes() |
// Example with all placeholders
func (receiver *Between) Message(ctx context.Context) string {
return "The :attribute value :value must be between :option0 and :option1."
}Register Custom Rules
A new rule created by make:rule will be registered automatically in the bootstrap/rules.go::Rules() function and the function will be called by WithRules. You need register the rule manually if you create the rule file by yourself.
func Boot() contractsfoundation.Application {
return foundation.Setup().
WithRules(Rules).
WithConfig(config.Boot).
Create()
}Available Validation Filters
Validation filters use snake_case names by default. Filters run before validation and can be declared on form requests or with validation.Filters when manually creating validators.
| Category | Filters |
|---|---|
| String Cleaning | trim, ltrim, rtrim |
| Case Conversion | lower, upper, title, ucfirst, lcfirst |
| Naming Style | camel, snake |
| Type Conversion | to_int, to_int64, to_uint, to_float, to_bool, to_string, to_time |
| Short Type Aliases | int, int64, uint, float, bool |
| Escaping / Encoding | strip_tags, escape_js, escape_html, url_encode, url_decode |
| String Splitting | str_to_ints, str_to_array, str_to_time |
validator, err := facades.Validation().Make(ctx, input, rules, validation.Filters(map[string]any{
"name": "trim",
"age": []string{"trim", "to_int"},
}))Deprecated Filter Aliases
The following aliases remain backward-compatible, but will be removed in the next major version. Prefer the new snake_case names:
| Deprecated | Use Instead |
|---|---|
trimSpace | trim |
trimLeft | ltrim |
trimRight | rtrim |
lowercase | lower |
uppercase | upper |
lcFirst, lowerFirst | lcfirst |
ucFirst, upperFirst | ucfirst |
ucWord, upperWord | title |
camelCase | camel |
snakeCase | snake |
toInt, integer | to_int |
toUint | to_uint |
toInt64 | to_int64 |
toFloat | to_float |
toBool | to_bool |
toString | to_string |
toTime, str2time, strToTime | to_time or str_to_time |
escapeJs, escapeJS | escape_js |
escapeHtml, escapeHTML | escape_html |
urlEncode | url_encode |
urlDecode | url_decode |
stripTags | strip_tags |
str2ints, strToInts | str_to_ints |
str2arr, str2array, strToArray | str_to_array |
Custom Filters
Goravel provides a variety of helpful filters, however, you may wish to specify some of your own.
Creating Custom Filters
To generate a new filter object, you can simply use the make:filter Artisan command. Let's use this command to generate a filter that converts a string to an integer. This filter is already built into the framework, we just create it as an example. Goravel will save this new filter in the app/filters directory. If this directory does not exist, Goravel will create it when you run the Artisan command to create the filter:
./artisan make:filter ToInt
./artisan make:filter user/ToIntDefining Custom Filters
One filter contains two methods: Signature and Handle. The Signature method sets the name of the filter. The Handle method performs the specific filtering logic:
package filters
import (
"context"
"github.com/spf13/cast"
)
type ToInt struct {
}
// Signature The signature of the filter.
func (receiver *ToInt) Signature() string {
return "to_int_custom"
}
// Handle defines the filter function to apply.
func (receiver *ToInt) Handle(ctx context.Context) any {
return func (val any) int {
return cast.ToInt(val)
}
}Register Custom Filters
A new filter created by make:filter will be registered automatically in the bootstrap/filters.go::Filters() function and the function will be called by WithFilters. You need register the filter manually if you create the filter file by yourself.
func Boot() contractsfoundation.Application {
return foundation.Setup().
WithFilters(Filters).
WithConfig(config.Boot).
Create()
}