bkwld/laravel-haml
bkwld/laravel-haml brings Haml templating to Laravel, integrating with the framework's view system so you can write clean, indentation-based markup that compiles to HTML. It reduces boilerplate and keeps Laravel views concise, readable, and easier to maintain.
composer require bkwld/laravel-haml
Bkwld\LaravelHaml\ServiceProvider is auto-discovered. On older versions add it to config/app.php under providers.php artisan vendor:publish --provider="Bkwld\LaravelHaml\ServiceProvider"
This drops config/laravel-haml.php, which controls the MtHaml environment, compilation options, and any registered extensions.resources/views/welcome.haml:
!!! 5
%html
%body
%h1= $title
%p Welcome, #{$name}!
Then render it exactly like any Blade view:
Route::get('/', fn() => view('welcome', ['title' => 'Hi', 'name' => 'Ada']));
The package registers .haml (and optionally .hamlc) as a view extension, so view(), View::make(), and @include all resolve HAML files transparently. Compiled output lands in storage/framework/views just like Blade, so there is no runtime overhead once warmed.
First use case to try: a static marketing/landing page, or any view where the Blade markup feels repetitive. HAML's indentation-driven syntax shines on nested HTML; it's a poor fit for JS-heavy or heavily componentized templates.
Layouts and partials. MtHaml does not ship Laravel-style @extends/@section. Two common approaches:
auto_include option to prepend a shared partial (header, layout scaffold) to every compile.!= view('partials.nav')->render() — the != suppresses escaping, and the sub-render goes through whichever engine that view uses (HAML or Blade). This is the cleanest way to compose a HAML partial inside a HAML page.Interpolating PHP. HAML supports raw PHP lines with a leading -:
- foreach ($posts as $post)
%article
%h2= $post->title
Escaped output uses =, unescaped uses !=. Treat != as a deliberate escape hatch, exactly like Blade's {!! !!} — the same XSS rules apply.
Passing data. Nothing changes from Blade: view('page', ['user' => $user]) (or compact, or a View::composer) works identically. The package only swaps the compiler; the view factory, finder, and composers are untouched.
Registering Laravel helpers as HAML extensions. Out of the box, route(), asset(), and csrf_field() are not first-class HAML constructs — you call them as PHP (= route('home')). For ergonomics, register a small custom MtHaml\NodeVisitor or use the config's extensions array to add helpers so templates can say %a{href: url('home')}. This is the main extension point and worth investing in early for a real project.
Mixing with Blade. Because FileViewFinder tries each registered extension, @include('partials.footer') will pick up footer.haml if footer.blade.php doesn't exist. This lets you migrate incrementally — convert one view at a time without rewriting the rest.
Caching. Compilation is cached identically to Blade. In development, HAML files recompile on change (mtime-based). In production, they are compiled on first hit; warm them during deploy with a request to the app or by calling view()->exists() across your views.
{{ }}, @if, @foreach, @csrf all do nothing meaningful — the file is fed to MtHaml first, not Blade. Use PHP (- if (...), = $var) or HAML's own syntax. Mixing Blade into a .haml file is a common first-day error.MtHaml\Exception at compile time with a line number in the source file. If you don't see the line, php artisan view:clear and re-request — stale cached files sometimes hide the real error.storage/framework/views/*.php contains the exact PHP produced from your HAML. When a template renders unexpectedly, reading the compiled file is the fastest way to see what escaped and what didn't.view:clear. The compiler settings (escaping, environment, extensions) are baked into compiled files; changing config/laravel-haml.php without clearing the view cache silently keeps old behavior.environment matters. The config's environment value (e.g., php) controls which MtHaml compiler runs — PHP versus Twig-style backends. Getting this wrong produces odd syntax errors on valid templates.= escapes HTML but does not implement Blade's Htmlable/Blade::render semantics. Objects with a __toString or an Htmlable interface render as-is only when the HAML compiler treats them as strings — verify with a quick test rather than assuming parity with Blade.= csrf_field() and = route(...) in every file, add a NodeVisitor/helper via the config's extensions array. It's the intended escape hatch for a project's specific helper vocabulary.How can I help you explore Laravel packages today?