@error

Renders a block when a field has a validation error and exposes its first message as $message.

Signature
@error($attribute, $bag = 'default') ... @else ... @enderror
  • Added in Laravel 5.8

Parameters

ParameterTypeDescription
$$attributestringThe field name to check, in dot notation for nested or array inputs (items.0.quantity).
$$bagoptionalstring= 'default'The name of the error bag to read, for pages that carry more than one form.

Return value

Renders the enclosed block when the given attribute has at least one message in the error bag, with $message set to the first message for that attribute.

Errors

The compiled code reads the $errors variable, so a view rendered outside the web middleware group — where ShareErrorsFromSession does not run — fails with an undefined variable error.

Changelog

  • PHP 5.8Directive introduced; documented in the Blade "Validation Errors" section with the single-attribute form.
  • PHP 6.xSecond parameter documented, allowing a specific named error bag to be read on pages containing multiple forms.

The directive compiles to an if statement over the shared $errors bag: it checks $errors->getBag($bag)->has($attribute) and, inside the block, sets $message to first($attribute). Reach for it when you want the message attached to the field it belongs to rather than one list of problems at the top of the form.

Because it is a plain conditional, it works in two places at once. Inline in an attribute it toggles a class:

class="@error('title') is-invalid @enderror"

and as a block it prints the message. The docs also show @else, so you can render an is-valid state when the field is clean.

What trips people up

  • Only the first message. $message is a single string, not the list. A field failing three rules still renders one line.
  • Dot notation, not brackets. An input named items[0][quantity] is keyed items.0.quantity in the bag.
  • $message is scoped. Blade unsets it at @enderror and restores any variable of that name you already had, so it is not readable after the block.
  • Nothing renders on a fresh GET. Messages only exist after a redirect back that flashed them to the session.

Example

Example · php
<?php

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;

// In a real request this bag is flashed to the session by
// $request->validate() and shared with every view automatically.
$errors = (new ViewErrorBag)->put('default', new MessageBag([
    'title' => ['The title field is required.'],
]));

$template = <<<'BLADE'
<label for="title">Post Title</label>

<input id="title" name="title" type="text" class="@error('title') is-invalid @enderror">

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
BLADE;

echo Blade::render($template, ['errors' => $errors]);

// Renders:
// <label for="title">Post Title</label>
//
// <input id="title" name="title" type="text" class=" is-invalid ">
//
//     <div class="alert alert-danger">The title field is required.</div>

When to use it

  • Add an is-invalid class to the one input that failed after a redirect back, instead of styling the whole form.
  • Print an inline message directly under a field rather than a single error summary at the top of the page.
  • Keep login and registration messages apart on a page holding both forms by reading a named error bag.
  • Highlight one row of a repeated field group with a dot-notation key such as items.2.quantity.

Frequently asked questions

How do I show all validation messages for one field?

Loop over $errors->get('field') inside the block, because $message is only the first message for that attribute. The directive calls first() on the bag, so a field failing three rules still yields one string; get() returns the full array and can be iterated with @foreach.

Why is $errors undefined in my Blade view?

Because the view was rendered outside the web middleware group, where the ShareErrorsFromSession middleware never ran. That middleware is what shares the ViewErrorBag with every view. Views rendered from console commands, queued mailables, or API routes must be passed an errors variable explicitly, or the route must move into the web group.

How do I check an array input like items[0][name]?

Use dot notation: @error('items.0.name'). Laravel normalises nested request input to dot keys in the message bag, so passing the HTML bracket form matches nothing and the block silently never renders. The same applies to nested objects such as address.postcode.

More examples

Where the messages come from

Shows the full round trip. Nothing renders inside the block on the first GET; the block only fires on the redirect back that validate() triggers, and old('title') repopulates the input at the same time. The @else branch marks the field valid once it passes.

Example · php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    public function store(Request $request)
    {
        // On failure this throws ValidationException, which redirects
        // back and flashes the bag that @error later reads.
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:120'],
            'body'  => ['required'],
        ]);

        echo 'validated: ';
        print_r(array_keys($validated));

        return redirect('/posts');
    }
}

// The matching view, resources/views/post/create.blade.php:
//
// <input name="title" value="{{ old('title') }}"
//        class="@error('title') is-invalid @else is-valid @enderror">
//
// @error('title')
//     <p class="invalid-feedback">{{ $message }}</p>
// @enderror

Two forms on one page with named bags

Both bags carry an email key, yet each block reads only the bag it names, so the login message never leaks into the register form. The third block prints nothing: it reads the default bag, which was never populated here.

Example · php
<?php

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;

// A controller produces these with:
//   return back()->withErrors($validator, 'login');
$errors = new ViewErrorBag;
$errors->put('login', new MessageBag([
    'email' => ['These credentials do not match our records.'],
]));
$errors->put('register', new MessageBag([
    'email' => ['The email has already been taken.'],
]));

$template = <<<'BLADE'
@error('email', 'login')<p class="login-error">{{ $message }}</p>@enderror
@error('email', 'register')<p class="register-error">{{ $message }}</p>@enderror
@error('email')<p class="default-error">{{ $message }}</p>@enderror
BLADE;

echo Blade::render($template, ['errors' => $errors]);

// <p class="login-error">These credentials do not match our records.</p>
// <p class="register-error">The email has already been taken.</p>

Array fields and showing every message

Two things worth copying. The HTML name items[0][quantity] is keyed items.0.quantity in the bag, so the bracket form never matches. And $message would print only the first of the two lines, so the block loops $errors->get() to render both list items.

Example · php
<?php

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;

$errors = (new ViewErrorBag)->put('default', new MessageBag([
    'items.0.quantity' => [
        'The items.0.quantity must be an integer.',
        'The items.0.quantity must be at least 1.',
    ],
]));

$template = <<<'BLADE'
<input name="items[0][quantity]"
       class="@error('items.0.quantity') is-invalid @else is-valid @enderror">

@error('items.0.quantity')
    <ul>
    @foreach ($errors->get('items.0.quantity') as $line)
        <li>{{ $line }}</li>
    @endforeach
    </ul>
@enderror
BLADE;

echo Blade::render($template, ['errors' => $errors]);

Discussion

  • Be the first to comment on this lesson.