Mike Rockétt

Custom Messages in Laravel Form Requests

From Laravel 5.5, JSON responses are a little different. In particular, you’ll find that validation exceptions have changed, and it’s easy to miss what the upgrade guide says about it.

#Laravel — 20 December 2020

In versions prior to 5.5, a JSON response for a failed form request might look something like this:

{
"name": ["The name field is required"],
"email": ["The email field is required"],
}

That was super simple enough for us to get the errors from response.data in an XHR request. But, Laravel 5.5 does things more differently, in what appears to be a more opinionated way of responding to requests (I guess there’s a good reason for it, so I’m not complaining…) Here’s what the response might look like now:

{
"errors": {
"name": ["The name field is required."],
"email": ["The email field is required."],
...
},
"message": "The given data was invalid."
}

It’s actually a good thing. If you’re looking to create a solid API, the best place to get your error message from would be the server, considering you’re collecting the validation errors from there too. Keep it consistent, Sam.

But here’s the problem

Form requests don’t give you an easy way to change message, in the case you’d like to use a custom message per request type, or perhaps localise it to the user’s current language. Why? Because the failedValidation method throws a ValidationException which, when constructed, harcodes the exception message.

So, naturally, you’d want to override that method. Here’s what the 5.5 upgrade guide says:

If you were customizing the response format of an individual form request, you should now override the failedValidation method of that form request, and throw an HttpResponseException instance containing your custom response.

And it gives this example:

use Illuminate\Http\Exceptions\HttpResponseException;
 
/**
* Handle a failed validation attempt.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(..., 422));
}

That’s all well and good, but let’s keep things DRY, and use a trait that overrides the method in question. We’ll also take it a step further by allowing each form request that uses the trait to specify which message should be used (and, if it isn’t used, then we should fall back to the default).

Note: Given the way in which we’re overriding the method to use a different kind of exception, this is only suitable for API cases. If you’re not using XHR requests and relying on normal page-to-page requests (redirection), then this won’t work out for you. You’d need to use a custom version of ValidationException instead…

The Trait

Start with a Trait in your app/Http/Requests/Traits directory. Let’s call it UsesCustomErrorMessage:

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
 
trait UsesCustomErrorMessage
{
protected function failedValidation(Validator $validator)
{
$message = (method_exists($this, 'message'))
? $this->container->call([$this, 'message'])
: 'The given data was invalid.';
 
throw new HttpResponseException(response()->json([
'errors' => $validator->errors(),
'message' => $message,
], 422));
}
}

For the first argument, we’re binding $validator to the applicable contract via Dependency Injection. This is necessary as we need to match the original signature of the method.

At the beginning of the method, we’re setting a $message. As this is a trait, it obviously inherits the form request, and so all we need to do is check that it has a method called message. If there is one, then we use that for the exception message, otherwise we fall back to the default message.

We then throw the exception with a JSON response that contains the validator’s error bag and the $message.

Usage

Now that we’ve created the trait, we can use it in any form request, like this:

use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Traits\UsesCustomErrorMessage;
 
class StoreProfileRequest extends FormRequest
{
use UsesCustomErrorMessage;
 
public function message()
{
return 'Please check the errors below.';
}
}

And there you have it! You should now receive a response that looks like this:

{
"errors": {
"name": ["The name field is required."],
"email": ["The email field is required."],
},
"message": "Please check the errors below."
}

Alternative approach

If you don’t like traits so much, you could always create a BaseFormRequest class that extends Illuminate\Foundation\Http\FormRequest, and add the above method and contract to it. Then it’s as simple as having all your form requests extend the base request instead of the default one.