@csrf

Outputs the hidden token field every POST, PUT, PATCH or DELETE form needs to pass CSRF checks.

Signature
@csrf

Return value

stringA hidden input element carrying the current session's CSRF token — equivalent to the csrf_field() helper.

Errors

Rendering it without a started session throws a RuntimeException, because there is no token to read.

@csrf emits the hidden _token field that Laravel's CSRF middleware looks for. The documentation states the equivalence directly:

<form method="POST" action="/profile">
    @csrf

    <!-- Equivalent to... -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>

When you need it

Every POST, PUT, PATCH and DELETE form in a route that uses the web middleware group. Illuminate\Foundation\Http\Middleware\ValidateCsrfToken compares the submitted token against the one in the session; a mismatch aborts the request with a 419 response, which Laravel renders as "Page Expired".

What trips people up

The token is tied to the session, and the session regenerates on login and logout. A login form left open in a tab overnight, or a page served from a full-page cache, submits a stale token and gets a 419. The fix is to refresh the page, not to disable the middleware.

AJAX requests do not get a hidden field. Put the token in a meta tag — <meta name="csrf-token" content="{{ csrf_token() }}"> — and send it as the X-CSRF-TOKEN header; Axios reads the encrypted XSRF-TOKEN cookie and sets X-XSRF-TOKEN for you.

For third-party webhooks, exclude the route rather than dropping the token: $middleware->validateCsrfTokens(except: ['stripe/*']) in bootstrap/app.php.

Example

Example · php
<?php

// resources/views/profile/edit.blade.php
$form = <<<'BLADE'
<form method="POST" action="/profile">
    @csrf

    <input type="text" name="name" value="{{ $user->name }}">
    <button type="submit">Save</button>
</form>
BLADE;

// @csrf compiles to a call to the csrf_field() helper:
echo csrf_field();
// <input type="hidden" name="_token" value="kQ3v...9Zt">

When to use it

  • Protect a profile update form posted from a web route
  • Emit the token inside a delete-confirmation form built by a partial
  • Feed the same token to a meta tag so AJAX calls can send the X-CSRF-TOKEN header

Frequently asked questions

What does @csrf output in a Blade template?

A hidden input named _token holding the current session's CSRF token — the same thing as writing <input type="hidden" name="_token" value="{{ csrf_token() }}">. It compiles to a call to the csrf_field() helper.

Why do I get a 419 Page Expired after submitting a form?

The submitted token did not match the session's, usually because the session expired, the page was cached, or the user logged in or out in another tab. Reload the page to obtain a fresh token.

Do I need @csrf on a GET form?

No. CSRF verification applies to POST, PUT, PATCH and DELETE requests, so a GET form needs no token field.

More examples

Token for AJAX requests

The middleware checks the X-CSRF-TOKEN header as well as the _token field, so a fetch or jQuery call reads the value out of the meta tag. Axios needs none of this: Laravel sets an encrypted XSRF-TOKEN cookie on every response and Axios copies it into the X-XSRF-TOKEN header on same-origin requests.

Example · php
<?php

// resources/views/layouts/app.blade.php
$head = <<<'BLADE'
<meta name="csrf-token" content="{{ csrf_token() }}">
BLADE;

// resources/js/app.js
$js = <<<'JS'
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});
JS;

echo $head;
// <meta name="csrf-token" content="kQ3v...9Zt">

Excluding a webhook route

A payment provider posting a webhook has no session and no token, so its route must be excluded. The documented preference is to keep such routes outside the web middleware group entirely; the except list is the fallback when the route has to live alongside session-based pages.

Example · php
<?php

// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->validateCsrfTokens(except: [
            'stripe/*',
            'http://example.com/foo/bar',
            'http://example.com/foo/*',
        ]);
    })->create();

echo 'CSRF verification skipped for the listed URIs.';

Why a cached form 419s

The directive is present and correct; the caching layer is the bug. Either exclude pages containing forms from full-page caching, or render the form's token client-side from a cookie or a fresh endpoint. The same failure mode explains 419s on pages restored from the browser's back-forward cache after a logout.

Example · php
<?php

// A response cached at the edge freezes one visitor's token into the HTML.
$cachedHtml = <<<'HTML'
<form method="POST" action="/subscribe">
    <input type="hidden" name="_token" value="TOKEN_FOR_VISITOR_A">
    <input type="email" name="email">
</form>
HTML;

// Visitor B receives the same HTML but has a different session token,
// so ValidateCsrfToken aborts with 419 Page Expired.
echo $cachedHtml;

Discussion

  • Be the first to comment on this lesson.