Introduction to Blade Templates

+15 Mana ✨

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.php file in resources/views/ that Laravel compiles to cached PHP at request time.
  • Escaped echo ({{ }}): Blade's default output syntax. Automatically runs values through htmlspecialchars() 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.php extension
  • 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:

php
class 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: &lt;script&gt;alert("XSS")&lt;/script&gt; -->

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>&copy; {{ $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

php
if (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

  1. Using {!! !!} on user-provided data — Unescaped output is an XSS vector. Only use it for content you've already sanitized.
  2. Forgetting dot notation for subdirectories — view('posts.show') resolves to resources/views/posts/show.blade.php, not posts/show.blade.php as a string.
  3. 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

  1. Default to {{ }} — Reach for {!! !!} only when you explicitly trust the HTML source.
  2. Group related views in subdirectories — posts/index.blade.php, posts/show.blade.php, etc. Keeps resources/views/ scannable.
  3. Prefer View Composers to controller duplication — If three controllers pass the same data, bind it once in a composer.

Summary

  • Blade compiles .blade.php files 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::share and View Composers centralize data that every view needs.
✓ Completed