Skip to content

Adding custom error messages to Joi js validation

Joi is a powerful JavaScript validation library. You can use it to validate the input you receive to your API, among other uses.

While it comes pretty good error messages out of the box, let’s see some ways to customize them.

You can test any of the examples below using Joi’s playground.

Adding custom labels

Let’s define a schema that accepts a matrix 2D array field.

Joi.object({
  matrix: Joi.array().items(Joi.array().items(Joi.number()))
})

The following object wouldn’t pass validation because it has a string a among its elements.

{ 
 matrix: [[0, 1, 2], ['a', 1, 2]]
}

By default, Joi returns the following error message:

Validation Error: "matrix[1][0]" must be a number

The error message includes the full path of the invalid value. The top-most key is matrix. Then, each array element is identified by its array index, starting from 0.

We can override the default key with label:

Joi.object({
  matrix: Joi.array().items(
    Joi.array().items(Joi.number().label("matrix value"))
  ),
});
Validation Error: "matrix value" must be a number

We can do the same for any key. Or even specify labels for each array element.

For example, let’s add a label for the elements passed to the matrix array.

Joi.object({
  matrix: Joi.array().items(
    Joi.array().items(Joi.number()).label("matrix row")
  ),
});

Now, the error message will be::

{ 
 matrix: [[0, 1, 2], 'a'],
}
Validation Error: "matrix row" must be an array

Return the key name instead of full path in error messages

Let’s define the following deeply nested schema:

const schema = Joi.object({
  a: {
    b: {
      c: {
        d: {
          e: Joi.number(),
        },
      },
    },
  },
});

Validating the following object, yields the error message:

const obj = {
  a: {
    b: {
      c: {
        d: {
          e: "a",
        },
      },
    },
  },
};
Validation Error: "a.b.c.d.e" must be a number

See the faulty field is identified with its full path in the object.

We can tweak this behavior by setting the errors.label option to key. The default value is path.

schema.validate(obj, {errors: {label: 'key'}})
Validation error: "e" must be a number

Additionally, we can delete the quotes that wrap the label with the errors.wrap option:

schema.validate(obj, {errors: {label: 'key', wrap: {label: false}}})
Validation error: e must be a number

See more information about the wrap and label options in the any.validate documentation

Add custom error messages to Joi validation

To pass custom error messages, we first need to identify the associated error type. For example, a numerical field being less than the specified minimum value is associated to a number.min error type.

Once we have identified the error type, we can ask Joi to override the predefined error message with the messages method.

Joi.object({
  age: Joi.number()
    .integer()
    .min(18)
    .messages({ "number.min": "You must be at least 18 years old" }),
});

See the messages method accepts an object where each key corresponds to the error type and the value is the desired error message.

With this configuration, the following object will return the error message:

{ 
 age: 1,
}
Validation Error: You must be at least 18 years old

You can find the full list of error types in Joi’s documentation.

Referencing the values of other fields in error messages

Let’s include the actual field values in the error message. We can do so with Joi’s templating syntax.

Let’s create a schema that requires both a name and age fields.

Joi.object({
  name: Joi.string().required(),
  age: Joi.number()
    .required()
    .messages({ "any.required": "You must tells us your age, {name}" }),
});

If we don’t set the age, Joi will return an error message with the value passed to the name field. See that the field names was wrapped inside {}.

{ 
 name: "John",
}
Validation Error: You must tells us your age, John

You can find more about template syntax in Joi’s documentation.

Referencing fields in deeply nested objects

Let’s modify the schema above by nesting age inside a data field.

Joi.object({
  name: Joi.string().required(),
  data: {
    age: Joi.number()
      .required()
      .messages({ "any.required": "You must tells us your age, {name}" }),
  },
});

Now, let’s pass the following object to validate:

{
  name: "John",
  data: {},
}

We’ll receive a custom error message, but Joi didn’t catch the name this time.

Validation Error: You must tells us your age, 

By default, the key will recognize only siblings of the validated field. In our case, those are all the properties of the data field. To be more specific we have to use Joi references.

A simple solution in our case is to reference the parent of name with relative references:

Joi.object({
  name: Joi.string().required(),
  data: {
    age: Joi.number()
      .required()
      .messages({ "any.required": "You must tells us your age, {...name}" }),
  },
});

See that we’re prepending the field name with three dots: ...name.

This is s similar to the rules of path resolution in the command line. .. references the parent field (default behavior), and . references the current field. We can go higher up the hierarchy by prepending additional dots.

Published inProgramming
Subscribe
Notify of
guest
2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Tony
Tony
1 year ago

Googling and googling and I ended up here cuz there are no good docs even in joi site, this post crearly stated the things that I needed to know

Thank you very much!