Introduction
Blade is Laravel's templating engine — compiled to plain PHP and cached, so it adds essentially zero runtime overhead. It gives you familiar curly-brace echo syntax, automatic HTML escaping, and a small set of helper directives that keep views readable without forcing you to drop down into raw PHP.
Key Concepts
- Blade template: A
.blade.phpfile inresources/views/that Laravel compiles to cached PHP at request time. - Escaped echo (
{{ }}): Blade's default output syntax. Automatically runs values throughhtmlspecialchars()to defend against XSS. - View data: Values passed from a route or controller to a view via
view('name', [...]). - View composer: A callback bound to one or more view names that injects shared data whenever those views render.
Real World Context
Every Laravel application that renders HTML uses Blade. Understanding how views are resolved, how data is passed, and how Blade escapes output is the baseline for every server-rendered page you'll build — from a simple landing page to an admin dashboard.
Deep Dive
Blade is Laravel's powerful templating engine. Unlike other PHP templating engines, Blade doesn't restrict you from using plain PHP—but it provides convenient shortcuts that make your templates cleaner and more readable.
What is Blade?
Blade templates:
- Use the
.blade.phpextension - Live in
resources/views/ - Are compiled into plain PHP and cached
- Add essentially zero overhead to your application
Creating Views
Views are stored in resources/views/:
resources/views/
├── welcome.blade.php
├── layouts/
│ └── app.blade.php
├── posts/
│ ├── index.blade.php
│ ├── show.blade.php
│ └── create.blade.php
└── components/
└── alert.blade.php
Returning Views
From routes:
php// Simple view Route::get('/', function () { return view('welcome'); }); // Nested view (posts/index.blade.php) Route::get('/posts', function () { return view('posts.index'); });
From controllers:
phpclass PostController extends Controller { public function index() { $posts = Post::all(); return view('posts.index', ['posts' => $posts]); } // Alternative syntax public function show(Post $post) { return view('posts.show', compact('post')); } // Using with() public function create() { return view('posts.create') ->with('categories', Category::all()); } }
Displaying Data
Blade's double curly braces echo data with automatic HTML escaping:
blade<!-- Escaped output (safe from XSS) --> <h1>{{ $title }}</h1> <p>{{ $user->name }}</p> <p>{{ $user->bio ?? 'No bio provided' }}</p> <!-- With HTML entities escaped --> {{ '<script>alert("XSS")</script>' }} <!-- Output: <script>alert("XSS")</script> -->
Unescaped Data
For trusted HTML content (use carefully!):
blade<!-- Unescaped - ONLY for trusted content --> {!! $post->html_content !!} <!-- WARNING: This is vulnerable to XSS if data isn't sanitized -->
PHP in Blade
You can use raw PHP when needed:
blade@php $currentYear = date('Y'); $greeting = $hour < 12 ? 'Good morning' : 'Good afternoon'; @endphp <footer>© {{ $currentYear }}</footer>
Comments
blade{{-- This comment will NOT appear in the HTML output --}} <!-- This HTML comment WILL appear in the output -->
Verbatim (Escaping Blade)
For JavaScript frameworks that use {{ }}:
blade<!-- Single escape --> @{{ vueVariable }} <!-- Block escape --> @verbatim <div id="app"> {{ message }} {{ user.name }} </div> @endverbatim
Checking View Existence
phpif (View::exists('posts.show')) { return view('posts.show', $data); } // Or return first existing view return view()->first(['custom.posts', 'posts.show'], $data);
Sharing Data with All Views
php// In AppServiceProvider boot() View::share('appName', config('app.name')); View::share('currentUser', auth()->user()); // Now available in ALL views <title>{{ $appName }}</title>
View Composers
Automatically bind data to specific views:
php// In AppServiceProvider boot() View::composer('layouts.app', function ($view) { $view->with('notifications', auth()->user()?->unreadNotifications); }); // For multiple views View::composer(['posts.*', 'pages.*'], function ($view) { $view->with('categories', Category::all()); }); // Using a class View::composer('profile', ProfileComposer::class);
php// app/View/Composers/ProfileComposer.php class ProfileComposer { public function compose(View $view): void { $view->with('stats', [ 'posts' => auth()->user()->posts()->count(), 'followers' => auth()->user()->followers()->count(), ]); } }
Common Pitfalls
- Using
{!! !!}on user-provided data — Unescaped output is an XSS vector. Only use it for content you've already sanitized. - Forgetting dot notation for subdirectories —
view('posts.show')resolves toresources/views/posts/show.blade.php, notposts/show.blade.phpas a string. - Sharing mutable objects through
View::share— Shared data is computed once per request; passing a stateful service can lead to surprising cross-request bleed if you don't watch it.
Best Practices
- Default to
{{ }}— Reach for{!! !!}only when you explicitly trust the HTML source. - Group related views in subdirectories —
posts/index.blade.php,posts/show.blade.php, etc. Keepsresources/views/scannable. - Prefer View Composers to controller duplication — If three controllers pass the same data, bind it once in a composer.
Summary
- Blade compiles
.blade.phpfiles to PHP and caches them. {{ }}escapes output;{!! !!}does not — only use the latter on trusted HTML.- Views can be returned from routes and controllers with
view(name, data). @php,@verbatim, and comments let you mix raw PHP and client-side template syntax safely.View::shareand View Composers centralize data that every view needs.