# How to Validate Request Bodies with Custom Validators in Gin

> Learn to validate request bodies in Gin using custom validators. Extend the default validator to create efficient and tailored validation logic for your Go applications.

- Repository: [Gin-Gonic/gin](https://github.com/gin-gonic/gin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Gin validates request bodies through its `binding` package by unmarshaling data into structs and passing them to a global `Validator` instance that wraps go-playground/validator/v10, which you can extend by casting `binding.Validator.Engine()` to `*validator.Validate` and registering custom validation functions.**

Gin provides a robust validation pipeline for HTTP request bodies through its integration with the go-playground/validator library. Understanding how to validate request bodies with custom validators in Gin requires knowledge of the framework's binding architecture and the exposed validator engine. This guide examines the source code implementation in the gin-gonic/gin repository to show you how to register field-level and struct-level validation rules while leveraging the framework's automatic validation triggers.

## Understanding the Validation Pipeline

Gin's validation workflow follows a strict sequence from request binding to struct validation. When a handler calls `c.ShouldBind(&obj)` or any of its variants, the framework selects the appropriate `Binding` implementation based on the request's Content-Type (see the `Default` function in [`binding/binding.go`](https://github.com/gin-gonic/gin/blob/main/binding/binding.go) lines 93-120).

After the raw payload is unmarshaled into your struct, Gin invokes the private `validate` helper function (lines 22-27 of [`binding/binding.go`](https://github.com/gin-gonic/gin/blob/main/binding/binding.go)):

```go
func validate(obj any) error {
    if Validator == nil {
        return nil
    }
    return Validator.ValidateStruct(obj)
}

```

This helper forwards the struct to the global `Validator` variable, which must implement the `StructValidator` interface defined in lines 51-68 of the same file.

## Accessing the Underlying Validator Engine

By default, `binding.Validator` is an instance of `defaultValidator` declared at line 72 of [`binding/binding.go`](https://github.com/gin-gonic/gin/blob/main/binding/binding.go). This implementation wraps **go-playground/validator/v10** and exposes the underlying engine through the `Engine()` method (lines 81-88 of [`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go)):

```go
func (v *defaultValidator) Engine() any {
    v.lazyinit()
    return v.validate
}

```

The engine is created lazily using `sync.Once` (lines 90-95 of [`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go)) with the tag name set to `"binding"`. To register custom validators, you must cast this engine to `*validator.Validate`:

```go
import (
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
)

func init() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // Register custom validations here
        _ = v.RegisterValidation("custom_tag", myCustomFunc)
    }
}

```

## Creating Custom Field Validators

For single-field validation logic, use the `RegisterValidation` method. This approach is ideal for checking password complexity, custom string formats, or business-specific rules:

```go
import (
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
    "strings"
)

// passwordValidator ensures the field contains at least one digit and one special character
func passwordValidator(fl validator.FieldLevel) bool {
    pwd := fl.Field().String()
    hasDigit := strings.ContainsAny(pwd, "0123456789")
    hasSpecial := strings.ContainsAny(pwd, "!@#$%^&*")
    return hasDigit && hasSpecial
}

func init() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        _ = v.RegisterValidation("pwd", passwordValidator)
    }
}

```

Apply the custom tag in your struct definition:

```go
type SignUpForm struct {
    Username string `json:"username" binding:"required,alphanum"`
    Password string `json:"password" binding:"required,pwd,min=8"`
}

```

When Gin processes the request via `c.ShouldBindJSON(&form)`, the custom `pwd` validator executes automatically alongside the built-in rules.

## Implementing Cross-Field Validation

For validation logic that compares multiple fields (such as password confirmation), use `RegisterStructValidation`. This method receives the entire struct and can report errors on specific fields:

```go
type LoginForm struct {
    Email           string `json:"email" binding:"required,email"`
    Password        string `json:"password" binding:"required,min=8"`
    ConfirmPassword string `json:"confirm_password" binding:"required"`
}

func loginStructValidator(sl validator.StructLevel) {
    login := sl.Current().Interface().(LoginForm)
    if login.Password != login.ConfirmPassword {
        sl.ReportError(login.ConfirmPassword, "ConfirmPassword", "ConfirmPassword", "pwd_mismatch", "")
    }
}

func init() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        _ = v.RegisterStructValidation(loginStructValidator, LoginForm{})
    }
}

```

The `ReportError` method allows you to attach validation failures to specific struct fields while maintaining the standard Gin error response format.

## Key Source Files and Implementation Details

The validation pipeline spans several critical files in the gin-gonic/gin repository:

- **[`binding/binding.go`](https://github.com/gin-gonic/gin/blob/main/binding/binding.go)** (lines 29-36): Defines the `Binding` interface with `Bind(*http.Request, any) error` for each content type (JSON, XML, Form, etc.)
- **[`binding/binding.go`](https://github.com/gin-gonic/gin/blob/main/binding/binding.go)** (lines 51-68): Declares the `StructValidator` interface requiring `ValidateStruct(any) error` and `Engine() any`
- **[`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go)** (lines 43-70): Implements `ValidateStruct` with support for structs, pointers, and slices/arrays, including aggregation of slice element errors
- **[`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go)** (lines 81-88): Exposes the underlying `*validator.Validate` engine for custom rule registration
- **[`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go)**: Provides `*gin.Context` methods (`ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery`) that orchestrate the binding and validation flow

The `defaultValidator` handles type resolution automatically, validating pointers by dereferencing them and validating slices by iterating through elements, ensuring consistent behavior across complex nested structures.

## Summary

- **Gin uses a global `Validator` variable** that implements the `StructValidator` interface to validate request bodies after binding
- **The default implementation wraps go-playground/validator/v10** and exposes the engine via `Engine()`, allowing you to register custom validation functions without modifying the framework core
- **Cast `binding.Validator.Engine()` to `*validator.Validate`** to access `RegisterValidation` for field-level rules or `RegisterStructValidation` for cross-field logic
- **Validation triggers automatically** when calling `c.ShouldBind()`, `c.ShouldBindJSON()`, or other binding methods on the Gin context
- **Register custom validators in an `init()` function** to ensure they are available before the server handles requests

## Frequently Asked Questions

### How do I replace the default validator entirely in Gin?

Set the global `binding.Validator` variable to your own implementation of the `StructValidator` interface before starting your server. Your implementation must provide `ValidateStruct(any) error` and `Engine() any` methods. This completely overrides the default go-playground/validator integration with your custom logic.

### Can I use validation tags other than "binding" in struct tags?

The default validator engine is configured with the tag name `"binding"` as seen in [`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go) line 93. While you cannot change this tag name without replacing the entire validator, you can register multiple custom validation functions under different tag names (like `pwd` or `isodate`) and use them within the `binding` tag string separated by commas.

### How does Gin handle validation errors in slices or arrays?

According to the `ValidateStruct` implementation in [`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go) lines 43-70, Gin iterates through slice and array elements individually, validating each one and collecting errors. This ensures that every item in a request body array receives validation, not just the container itself.

### Where should I register custom validators to ensure they load before requests arrive?

Register custom validators in an `init()` function within a package imported by your main application. Since Gin initializes the validator engine lazily using `sync.Once` (lines 90-95 of [`binding/default_validator.go`](https://github.com/gin-gonic/gin/blob/main/binding/default_validator.go)), calling `binding.Validator.Engine()` in your `init()` triggers the initialization immediately, ensuring your custom rules are registered before the first request reaches a handler.