Taming Laravel Blade with Fully Typed Views, Autocomplete, and Type Safety



This content originally appeared on DEV Community and was authored by Raheel Shan

Blade is messy. We all know it. Developers pass associative arrays into views and switch between multiple partials, view and controller to remember what data is being passed. It works but sometmes it becomes cumbersome and frustrating.

I wasn’t okay with that. I wanted discipline in Blade—the same autocomplete and strict typing I get in PHP classes. And now I’ve taken it one step further: not just autocomplete, but hard guarantees that if you pass the wrong type of data into a view or partial, it explodes with a clear error message.

This article shows you how I did it.

Why Blade Fails Us

Laravel developers are used to Blade being loose. You pass an array into a view or use compact().

$posts = Post::all();

$data = [
    'posts' => $posts
];

return view('home', $data);

// or

$posts = Post::all();

return view('home', compact('posts'));

And inside home.blade.php you just assume $posts exists and is a collection of Post. But Blade doesn’t care. Your IDE doesn’t know. And if someone passes Comment::all() instead, you won’t find out until the UI looks broken.

This is silent failure at its worst.

My Approach: Enforce Types at Runtime

I built three simple building blocks:

  1. ResponseHelper – All controllers return through this helper. It ensures every response is either JSON (for API/Ajax) or a typed ViewModel (for Blade).
  2. TypedViewFactory – A custom view factory that parses @var declarations at the top of Blade files and enforces them.
  3. TypedViewServiceProvider – Wires everything into Laravel automatically.

The result: if you declare a type in a Blade file, Laravel must pass the correct class, array, or collection. Otherwise, it throws an exception.

Declaring Types in Blade

Here’s what it looks like:

Main View

{{-- home.blade.php --}}
@var App\ViewModels\HomeViewModel $model

<h1>{{ $model->title }}</h1>

If you pass anything other than `HomeViewModel` into this view, you get a clear exception.

Partial

{{-- _post.blade.php --}}
@var App\Models\Post $post

<div>{{ $post->title }}</div>

Now when you include `post`, it must receive a `Post` instance. If it gets a string, integer, or wrong class—it fails. Loudly.

Arrays and Collections with Generics

I didn’t stop at simple types. I wanted full support for arrays and collections.

@php
    /** @var \App\Models\Post $post */
@endphp

// Other example
@php
    /** @var \App\Models\Post[] $posts */
@endphp

// Yet another example
@php
/** @var \Illuminate\Support\Collection<\App\Models\Post> $posts */
@endphp

When you pass data, it doesn’t just check the container type. It checks every element:

  • If `$post` isn’t an Eloquent model, it fails.
  • If `$posts` contains anything other than an array of `Eloquent Post model` , it fails.
  • If `$posts` isn’t a `Collection`, it fails.

Exceptions in Action 🚨

This is the fun part. When something’s wrong, you get a clear error message. You get precision.

Case 1: Wrong type in main view

View [home] expects $model of type App\ViewModels\HomeViewModel, 
but got string.

And here’s the error view.

Main view needs HomeViewModel else throw error

Case 2: Wrong type in partial

View [partials.post] expects $post of type App\Models\Post, 
but got string.

Partial view needs Post model else throw error

The Code Behind It

Here’s the engine:

ResponseHelper

  • Ensures every controller hands off a proper `ViewModel`.
  • Handles JSON automatically for API/Ajax.

TypedViewFactory

  • Hooks into Laravel’s view system.
  • Reads `@var` annotations.
  • Validates data types (including arrays and collections).

TypedViewServiceProvider

  • Registers the custom view factory in Laravel.

I will soon provide the code in a package. Stay tuned.

Why This Matters

Typed views aren’t about being fancy. They’re about discipline.

Most Laravel apps loose their common behaviour in blade partials. Arrays of random data, fragile partials, missing fields nobody remembers. With typed views:

  • Your IDE knows what’s available.
  • Your code complains when you cheat.
  • Your future self won’t hate you.

Controllers must hand over the right ViewModel. Partials must receive the class they expect. Everything becomes predictable.

Final Thoughts

So yes, I had to ditch Laravel Intellisense and Laravel Intelliphence. They weren’t built for this. PHP Tools by DevSense  gave me autocomplete. And now, my Typed-View system enforces contracts in Blade at runtime.

If you’re tired of Blade being a guessing game, it’s time to try it yourself.

This is part of my bigger effort to bring structure and discipline to Blade. Check out my earlier posts if you haven’t already—and keep an eye out for the package release.

https://dev.to/raheelshan/avoid-the-mess-structuring-laravel-view-data-with-viewmodels-27nk

https://dev.to/raheelshan/how-to-enable-autocomplete-in-blade-partials-3ik9

If you found this post helpful, consider supporting my work — it means a lot.

Support my work


This content originally appeared on DEV Community and was authored by Raheel Shan