Legacy Validator docs
This is the documentation for the legacy version of the validator. For new apps we recommend using VineJS. However, for smoother upgrade experience, we maintain the legacy validator which brings the v5 validation module to v6 apps.
First, make sure to install @adonisjs/validator@next to use the legacy validator.
npm install @adonisjs/validator@next
yarn add @adonisjs/validator@next
pnpm add @adonisjs/validator@next
Also, make sure to add the following entry to your adonisrc.ts file.
providers: [
// ...other providers
() => import('@adonisjs/validator/validator_provider')
]
Introduction
import { schema } from '@adonisjs/validator'
import router from '@adonisjs/core/services/router'
router.post('posts', async ({ request }) => {
/**
* Schema definition
*/
const newPostSchema = schema.create({
title: schema.string(),
body: schema.string(),
categories: schema.array().members(schema.number()),
})
/**
* Validate request body against the schema
*/
const payload = await request.validate({ schema: newPostSchema })
})
The validator also extracts the static types from the schema definition. You get the runtime validations and the static type safety from a single schema definition.

Schema composition
The schema definition is divided into three main parts.
- The
schema.createmethod defines the shape of the data you expect. - The
schema.string,schema.number, and other similar methods define the data type for an individual field. - Finally, you use the
rulesobject to apply additional validation constraints on a given field. For example: Validating a string to be a valid email is unique inside the database.

The rules object is imported from @adonisjs/validator
import { schema, rules } from '@adonisjs/validator'
If you look carefully, we have separated the format validations from core data types. So, for example, there is no data type called schema.email. Instead, we use the rules.email method to ensure a string is formatted as an email.
This separation helps extend the validator with custom rules without creating unnecessary schema types that have no meaning. For example, there is no thing called email type; it is just a string, formatted as an email.
Working with optional and null values
All the fields are required by default. However, you can make use of the optional, nullable, and nullableAndOptional modifiers to mark fields as optional.
All of these modifiers serve different purposes. Let's take a closer look at them.
| Modifier | Validation behavior | Return payload |
|---|---|---|
optional | Allows both null and undefined values to exist | Removes the key from the return payload is not is non-existing |
nullable | Allows null values to exists. However, the field must be defined in the validation data | Returns the field value including null. |
nullableAndOptional | Allows both null and undefined values to exist. (Same as modifier 1) | Only removes the key when the value is undefined, otherwise returns the field value |
Use case for nullable modifier
You will often find yourself using the nullable modifier to allow optional fields within your application forms.
In the following example, when the user submits an empty value for the fullName field, the server will receive null, and hence you can update their existing full name inside the database to null.
schema: schema.create({
fullName: schema.string.nullable(),
})
Use case for nullableAndOptional modifier
If you create an API server that accepts PATCH requests and allows the client to update a portion of a resource, you must use the nullableAndOptional modifier.
In the following example, if the fullName is undefined, you can assume that the client does not want to update this property, and if it is null, they want to set the property value of null.
const payload = await request.validate({
schema: schema.create({
fullName: schema.string.nullableAndOptional(),
})
})
const user = await User.findOrFail(1)
user.merge(payload)
await user.save()
Use case for optional modifier
The optional modifier is helpful if you want to update a portion of a resource without any optional fields.
The email property may or may not exist in the following example. But the user cannot set it null. If the property is not in the request, you will not update the email.
const payload = await request.validate({
schema: schema.create({
email: schema.string.optional(),
})
})
const user = await User.findOrFail(1)
user.merge(payload)
await user.save()
Validating HTTP requests
You can validate the request body, query-string, and route parameters for a given HTTP request using the request.validate method. In case of a failure, the validate method will raise an exception.
import router from '@adonisjs/core/services/router'
import { schema, rules } from '@adonisjs/validator'
router.post('users', async ({ request, response }) => {
/**
* Step 1 - Define schema
*/
const newUserSchema = schema.create({
username: schema.string(),
email: schema.string([
rules.email()
]),
password: schema.string([
rules.confirmed(),
rules.minLength(4)
])
})
try {
/**
* Step 2 - Validate request body against
* the schema
*/
const payload = await request.validate({
schema: newUserSchema
})
} catch (error) {
/**
* Step 3 - Handle errors
*/
response.badRequest(error.messages)
}
})
We recommend NOT self-handling the exception and let AdonisJS convert the exception to a response using content negotiation.
Following is an explanation of how content negotiation works.
Server rendered app
If you build a standard web application with server-side templating, we will redirect the client back to the form and pass the errors as session flash messages.
Following is the structure of error messages inside the session's flash store.
{
errors: {
username: ['username is required']
}
}
You can access them using the flashMessages global helper.
@if(flashMessages.has('errors.username'))
<p> {{ flashMessages.get('errors.username') }} </p>
@end
Requests with Accept=application/json header
Requests negotiating for the JSON data type receive the error messages as an array of objects. Each error message contains the field name, the failed validation rule, and the error message.
{
errors: [
{
field: 'title',
rule: 'required',
message: 'required validation failed',
},
]
}
JSON API
Requests negotiating using Accept=application/vnd.api+json header, receives the error messages as per the JSON API spec.
{
errors: [
{
code: 'required',
source: {
pointer: 'title',
},
title: 'required validation failed'
}
]
}
Standalone validator usage
You can also use the validator outside of an HTTP request by importing the validate method from the Validator module. The functional API remains the same. However, you will have to manually provide the data to validate.
import { validator, schema } from '@adonisjs/validator'
await validator.validate({
schema: schema.create({
// ... define schema
}),
data: {
email: 'virk@adonisjs.com',
password: 'secret'
}
})
Also, since you perform the validation outside of an HTTP request, you will have to handle the exception and display the errors manually.
Validator classes
Validator classes allow you to extract the inline schema from your controllers and move them to a dedicated class.
You can create a new validator by executing the following Ace command.
node ace make:validator CreateUser
# CREATE: app/Validators/CreateUserValidator.ts
All the validation related properties, including the schema, messages are defined as properties on the class.
import { schema, CustomMessages } from '@adonisjs/validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class CreateUserValidator {
constructor (protected ctx: HttpContextContract) {
}
public schema = schema.create({
})
public messages: CustomMessages = {}
}
Using validator
Instead of passing an object with the schema property, you can now pass the class constructor to the request.validate method.
import router from '@adonisjs/core/services/router'
import CreateUser from '#validators/create_user_validator'
router.post('users', async ({ request, response }) => {
const payload = await request.validate(CreateUser)
})
During validation, a new instance of the validator class is created behind the scenes. Also, the request.validate method will pass the current HTTP context as a first constructor argument.
You can also manually construct the class instance and pass any arguments you like. For example:
router.post('users', async ({ request, response }) => {
const payload = await request.validate(
new CreateUser({
countries: fetchAllowedCountries(),
states: fetchAllowedStates()
})
)
})
Following is an example of using the validator classes outside of the HTTP request.
import { validator } from '@adonisjs/validator'
import CreateUser from '#validators/create_user_validator'
await validator.validate(
new CreateUser({
countries: fetchAllowedCountries(),
states: fetchAllowedStates()
})
)
What's next?
- Learn more about custom messages
- Learn more about error reporters