โ๏ธ 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
@switchand 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
@continueand@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.
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/@elsefor signed-in vs. guest@switchfor the role badge@canfor a permission-gated link- An inline ternary with
request()->routeIs()for the active link @forelse+$loop->first- A custom
@datetimedirective and@jsonto bootstrap JS
Best Practices
โ Do
- Keep views presentation-focused; push real logic to controllers or view composers
- Use
@auth/@caninstead of hand-rolled checks - Always include
@csrf(and@methodwhere 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
@canalone for security โ enforce it server-side too - Don't reach for
@phpwhen 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:
- In
AppServiceProvider::boot(), register acurrencydirective that outputs a dollar sign and two decimal places. - In a product view, loop over
$productswith@forelse. - Display each price with
@currency($product->price). - Only show an "Edit" link to users who
@can('update', $product). - 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/@guestand@production/@envtailor a view to the user and environment.@switchcleans up multi-way branching on a single value.@can,@cannot, and@cananygate UI by permission โ but always enforce authorization server-side too.- Secure forms need
@csrf;@methodspoofs PUT/PATCH/DELETE;@errorsurfaces validation messages. - Use
route()/asset()for URLs and@vitefor bundled assets โmix()is retired. @continue/@breakcontrol loops; register your own words withBlade::directive()orBlade::if().
๐ Further Reading
- Laravel 11 โ Blade Directives
- Laravel 11 โ Authorization in Blade
- Laravel 11 โ Asset Bundling (Vite)
๐ 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.