Skip to main content

โš™๏ธ Blade Directives and Control Structures

Directives are Blade's shortcuts โ€” small commands starting with @ that fold common tasks into one clean line. This lesson tours the directives you'll reach for daily: checking who's logged in, guarding actions by permission, wiring up safe forms, loading assets, controlling loops, and even inventing directives of your own.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Use authentication (@auth, @guest) and environment (@production, @env) directives
  • Branch cleanly with @switch and guard actions with authorization directives (@can, @cannot, @canany)
  • Build secure forms using @csrf, @method, and display validation errors with @error
  • Generate URLs and load bundled assets with asset(), route(), and @vite
  • Control loop flow with @continue and @break, and register a custom directive

Estimated Time: 35โ€“45 minutes  โ€ข  Difficulty: Intermediate

Hands-on: Write a custom @currency directive and use it in a view.

In This Lesson

A Map of Blade Directives

Every Blade directive begins with @ followed by a name. They fall into a handful of families โ€” control flow, template structure, auth, assets, and utilities. Think of them as the specialized bits in a toolkit: each one is shaped for a specific job, so your templates stay short and legible.

mindmap root((Blade Directives)) Control Flow if ยท elseif ยท else switch ยท case foreach ยท forelse continue ยท break Structure extends section ยท yield include ยท component Auth auth ยท guest can ยท cannot ยท canany Assets asset ยท route vite Utility csrf ยท method error ยท json ยท once Custom your own

You've already met the control-flow basics. This lesson focuses on the directives that connect a view to the rest of Laravel โ€” security, forms, and assets โ€” plus the tools to grow the language yourself.

Authentication & Environment

Who's logged in?

@auth renders its block only for signed-in users; @guest does the opposite. No manual if (Auth::check()) needed.

@auth
    <p>Welcome, {{ auth()->user()->name }}</p>
    <a href="{{ route('logout') }}">Log out</a>
@endauth

@guest
    <a href="{{ route('login') }}">Log in</a>
    <a href="{{ route('register') }}">Register</a>
@endguest

{{-- Check a specific guard --}}
@auth('admin')
    {{-- an admin is signed in --}}
@endauth

These act like a doorman who instantly knows whether a visitor holds a valid pass, and shows or hides controls accordingly.

Which environment?

Show content only in certain environments โ€” handy for debug banners or production-only analytics.

@production
    {{-- Only in production --}}
    <!-- analytics snippet -->
@endproduction

@env(['local', 'staging'])
    <div class="env-banner">{{ config('app.env') }} environment</div>
@endenv

Switch Statements

When you're branching on a single value across several cases, @switch reads more cleanly than a stack of @elseifs.

@switch($user->role)
    @case('admin')
        <span class="badge badge-danger">Administrator</span>
        @break

    @case('manager')
        <span class="badge badge-warning">Manager</span>
        @break

    @default
        <span class="badge badge-secondary">User</span>
@endswitch

It behaves exactly like PHP's switch โ€” remember the @break after each case, or execution "falls through" to the next one. It's a well-labelled sorting tray: drop a value in and it lands in exactly the right slot.

Authorization Directives

Authentication asks "are you logged in?"; authorization asks "are you allowed to do this?" Blade's @can family plugs straight into Laravel's Gates and Policies so a button only appears for users who may actually use it.

@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@elsecan('delete', $post)
    <a href="{{ route('posts.destroy', $post) }}">Delete</a>
@endcan

@cannot('update', $post)
    <p>You don't have permission to edit this post.</p>
@endcannot

Use @canany when either of several abilities should reveal a section:

@canany(['update', 'delete'], $post)
    <div class="post-actions">
        @can('update', $post)
            <a href="{{ route('posts.edit', $post) }}">Edit</a>
        @endcan
        @can('delete', $post)
            <a href="{{ route('posts.destroy', $post) }}">Delete</a>
        @endcan
    </div>
@endcanany

โš ๏ธ Hiding is not the same as protecting

@can only controls what's shown. A determined user can still POST to your edit route directly. Always enforce the same authorization on the server side too โ€” in the controller, a form request, or route middleware. The Blade directive is a courtesy to honest users, not a lock.

Form Directives

CSRF protection

Laravel rejects any state-changing request that lacks a valid CSRF token. The @csrf directive drops the required hidden field into your form.

<form method="POST" action="{{ route('profile.update') }}">
    @csrf
    {{-- ...fields... --}}
</form>

{{-- @csrf expands to: --}}
{{-- <input type="hidden" name="_token" value="..."> --}}

The token is a per-session seal that proves the submission came from your own site โ€” like a notary stamp that marks a document as genuine.

Spoofing HTTP methods

HTML forms only send GET or POST, but REST wants PUT, PATCH, and DELETE. @method tells Laravel to treat a POST as another verb.

<form method="POST" action="{{ route('posts.update', $post) }}">
    @csrf
    @method('PUT')
    {{-- ...fields... --}}
</form>

Showing validation errors

@error renders only when a given field failed validation, exposing the message as $message.

<label for="email">Email</label>
<input id="email" name="email" value="{{ old('email') }}">

@error('email')
    <p class="field-error">{{ $message }}</p>
@enderror

{{-- Or list every error at once --}}
@if ($errors->any())
    <ul class="alert alert-danger">
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif

Pair @error with old('email') so a rejected form redisplays what the user typed โ€” a proofreader that highlights mistakes without erasing the page.

Assets, URLs & Vite

Never hard-code a URL. Helpers keep links correct even if your domain or folder structure changes.

{{-- A file in the public directory --}}
<img src="{{ asset('images/logo.png') }}" alt="Logo">

{{-- A named route, with parameters --}}
<a href="{{ route('users.show', ['user' => $user->id]) }}">View profile</a>

{{-- A controller action --}}
<a href="{{ action([UserController::class, 'show'], ['user' => 1]) }}">Profile</a>

Bundled CSS & JS with @vite

Modern Laravel (10 and 11) uses Vite as its asset bundler โ€” the older mix() helper from Laravel Mix has been retired from new projects. The @vite directive loads your compiled entry points and handles cache-busting hashes automatically.

{{-- In your layout's <head> --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])

๐Ÿ’ก Dev vs. production

During npm run dev, @vite connects to the Vite dev server for instant hot-module replacement. On npm run build it emits hashed, minified links for production. You write the same one line either way.

Passing PHP data to JavaScript

@json serializes a PHP value to safe JSON โ€” no manual json_encode, and dangerous characters are escaped for embedding in a <script>.

<script>
    const user = @json($user);
    const settings = @json($settings, JSON_PRETTY_PRINT);
</script>

Loop Control & @once

Inside loops, @continue skips an iteration and @break exits early. Both accept a condition as an argument, so you rarely need a wrapping @if.

@foreach ($users as $user)
    @continue($user->is_deleted)   {{-- skip deleted users --}}
    @break($loop->iteration > 10)  {{-- stop after 10 --}}

    <li>{{ $user->name }}</li>
@endforeach

These give you a conductor's precision over which parts of a loop actually "play". @once, meanwhile, guarantees a block runs a single time per render โ€” perfect when a component is used many times but its script should be injected just once.

@once
    @push('scripts')
        <script src="{{ asset('js/date-picker.js') }}"></script>
    @endpush
@endonce

Custom Directives

When you find yourself repeating the same little snippet, you can teach Blade a new word. Register custom directives in the boot() method of a service provider (in Laravel 11 that's app/Providers/AppServiceProvider.php).

// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    // @datetime($value) โ€” format a Carbon date
    Blade::directive('datetime', function (string $expression) {
        return "<?php echo ($expression)->format('M j, Y g:i A'); ?>";
    });

    // @currency($amount) โ€” format money
    Blade::directive('currency', function (string $expression) {
        return "<?php echo '$' . number_format($expression, 2); ?>";
    });
}

The closure receives the raw expression you pass and returns the PHP that Blade will compile in its place. Then in any view:

<p>Posted: @datetime($post->created_at)</p>
<p>Price: @currency($product->price)</p>

๐Ÿ“– @if conditionals the easy way

For simple boolean directives, Blade::if() is even tidier than a raw Blade::directive() pair. It generates both the opening and @end forms for you:

Blade::if('admin', fn () => auth()->check() && auth()->user()->isAdmin());

Now @admin ... @endadmin just works in every view.

Worked Example: A Role-Aware Dashboard

This condensed dashboard view weaves together many directives at once โ€” auth checks, a switch for the role badge, an authorization gate, active-link highlighting, a @forelse with $loop, and a custom directive.

{{-- resources/views/dashboard.blade.php --}}
<aside class="sidebar">
    @auth
        <h3>{{ auth()->user()->name }}</h3>
        @switch(auth()->user()->role)
            @case('admin')
                <span class="role admin">Administrator</span> @break
            @case('moderator')
                <span class="role mod">Moderator</span> @break
            @default
                <span class="role user">Member</span>
        @endswitch
    @else
        <a href="{{ route('login') }}">Log in</a>
    @endauth

    <nav>
        <a href="{{ route('dashboard') }}"
           class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">Dashboard</a>

        @can('view-reports')
            <a href="{{ route('reports.index') }}">Reports</a>
        @endcan
    </nav>
</aside>

<main>
    @if (session('status'))
        <div class="alert alert-success">{{ session('status') }}</div>
    @endif

    <section class="stats">
        @forelse ($cards as $card)
            <div class="stat {{ $loop->first ? 'primary' : '' }}">
                <h4>{{ $card->title }}</h4>
                <p class="value">{{ $card->value }}</p>
                <small>Updated @datetime($card->updated_at)</small>
            </div>
        @empty
            <p>No statistics yet.</p>
        @endforelse
    </section>
</main>

@push('scripts')
    <script>
        const config = @json(['userId' => auth()->id()]);
    </script>
@endpush

Directives on display here

  • @auth/@else for signed-in vs. guest
  • @switch for the role badge
  • @can for a permission-gated link
  • An inline ternary with request()->routeIs() for the active link
  • @forelse + $loop->first
  • A custom @datetime directive and @json to bootstrap JS

Best Practices

โœ… Do

  • Keep views presentation-focused; push real logic to controllers or view composers
  • Use @auth/@can instead of hand-rolled checks
  • Always include @csrf (and @method where needed) in forms
  • Generate links with route()/asset(), never hard-coded strings
  • Extract repeated snippets into custom directives or components

โš ๏ธ Don't

  • Don't run database queries inside a Blade view
  • Don't rely on @can alone for security โ€” enforce it server-side too
  • Don't reach for @php when a directive or controller method would be clearer
  • Don't use the retired mix() helper in new Laravel projects โ€” use @vite

Hands-on Exercise

๐Ÿ‹๏ธ Write and Use a Custom @currency Directive

Objective: Register a directive that formats a number as US currency, then use it in a view.

Instructions:

  1. In AppServiceProvider::boot(), register a currency directive that outputs a dollar sign and two decimal places.
  2. In a product view, loop over $products with @forelse.
  3. Display each price with @currency($product->price).
  4. Only show an "Edit" link to users who @can('update', $product).
  5. Show "No products yet." for the empty case.
๐Ÿ’ก Hint

number_format($amount, 2) gives you the two-decimal formatting. The directive closure must return a string of PHP, e.g. "<?php echo '$' . number_format($expression, 2); ?>".

โœ… Sample solution
// AppServiceProvider.php
Blade::directive('currency', function (string $expression) {
    return "<?php echo '$' . number_format($expression, 2); ?>";
});
{{-- resources/views/products/index.blade.php --}}
@forelse ($products as $product)
    <div class="product">
        <h3>{{ $product->name }}</h3>
        <p>@currency($product->price)</p>
        @can('update', $product)
            <a href="{{ route('products.edit', $product) }}">Edit</a>
        @endcan
    </div>
@empty
    <p>No products yet.</p>
@endforelse

๐ŸŽฏ Quick Quiz

Question 1: Which two directives make a form able to send a PUT request safely?

Question 2: In a modern Laravel 11 project, how should you load compiled CSS and JS in your layout?

Question 3: Why is hiding a button with @can not enough to secure an action?

Summary & Quiz

๐ŸŽ‰ Key Takeaways

  • @auth/@guest and @production/@env tailor a view to the user and environment.
  • @switch cleans up multi-way branching on a single value.
  • @can, @cannot, and @canany gate UI by permission โ€” but always enforce authorization server-side too.
  • Secure forms need @csrf; @method spoofs PUT/PATCH/DELETE; @error surfaces validation messages.
  • Use route()/asset() for URLs and @vite for bundled assets โ€” mix() is retired.
  • @continue/@break control loops; register your own words with Blade::directive() or Blade::if().

๐Ÿ“š Further Reading

๐Ÿš€ What's Next?

Next we assemble these pieces into full pages: layout inheritance with @extends/@yield, stacks for scripts, and Blade components โ€” the modern, reusable building blocks of a Laravel UI.

๐ŸŽ‰ Well done!

Your directive vocabulary is strong. Time to structure whole layouts.