Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Handlebars Laravel Package

proai/laravel-handlebars

Laravel Handlebars integrates the Handlebars templating engine into Laravel, letting you render .hbs views through Laravel's view system with helpers, partials, layouts, and caching. Use it alongside Blade for clean, logic-light templates in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package and let Laravel auto-discover the service provider:

    composer require proai/laravel-handlebars
    php artisan vendor:publish --tag=handlebars-config
    
  2. Open config/handlebars.php first — it is the whole package in one file. You care about four keys:

    • where templates are discovered (typically resources/views, with a .hbs / .handlebars extension),
    • where compiled PHP is cached (storage/framework/... — must be writable),
    • the LightnCandy flags bitmask (this decides which Handlebars features exist),
    • the helpers and partials maps.
  3. Drop a template in and render it:

    // resources/views/emails/welcome.hbs
    // <h1>Hello {{user.name}}</h1>
    // {{#each items}}<li>{{this}}</li>{{/each}}
    
    use ProAI\Handlebars\Facades\Handlebars;
    
    return Handlebars::render('emails.welcome', [
        'user'  => ['name' => 'Ada'],
        'items' => ['A', 'B'],
    ]);
    
  4. When something doesn't behave, the source of truth is not this package — it's LightnCandy. Every flag, helper signature, and escaping rule comes from there. Read the LightnCandy README once and this wrapper becomes obvious.

Best first use case: transactional email or any HTML you also want to render client-side. That's the entire reason to accept the extra moving part.

Implementation Patterns

Blade-free server rendering. Use it where you want the same .hbs file rendered by PHP on first paint and by Handlebars.js in the browser afterwards. Keep the template free of Blade syntax — no @if, no {{ $var }}; Handlebars only understands {{var}}, {{#if}}, {{#each}}, {{#with}}.

Layouts via partials. Register partials in config (or at runtime) and compose:

// config/handlebars.php
'partials' => [
    'layout/header' => resource_path('views/partials/header.hbs'),
    'layout/footer' => resource_path('views/partials/footer.hbs'),
],
{{> layout/header}}
<main>{{{body}}}</main>
{{> layout/footer}}

Custom helpers for domain logic. Keep them small, pure, and in a dedicated service provider so they're testable independently of the template:

Handlebars::helper('money', fn ($amount) => number_format($amount / 100, 2) . ' €');
{{money order.total}}

Precompile on deploy, not on request. Compilation is expensive (it parses to an AST and emits PHP). In production, warm the cache during your deploy script — render each template once, or point the compile path at a directory you ship — and let the request path only include the compiled file. Turn on OPcache and this becomes effectively free.

Controller/view integration. If the package registers a view engine for the configured extension, return view('emails.welcome', $data); resolves to your .hbs file and you get Laravel's normal view pipeline (data binding, view:cache semantics). If you'd rather not rely on engine registration, wrap the facade in a single Renderer class in your app — one seam to mock in tests and one place to swap the backend later.

Eloquent data. Handlebars is dumb about objects; convert first.

$data = $order->load('items')->toArray(); // or ->toJson() then json_decode(..., true)

Models, Collection instances, and Carbon objects will not behave the way {{item.created_at}} expects. toArray() gives you plain nested arrays and predictable {{#each}} behaviour.

Testing. Assert on the rendered string, not on the compiled output:

$this->assertStringContainsString('Hello Ada', Handlebars::render('emails.welcome', $data));

Add a smoke test per template so a broken partial fails CI, not production.

Gotchas and Tips

  • Compiled templates are code. LightnCandy emits executable PHP. Never let user-supplied content reach the template source, and think twice before enabling flags that permit property/method access on objects (FLAG_PROPERTY, FLAG_METHOD). Treat templates as source code under version control, not as data.
  • Stale cache is the #1 bug report. Editing a .hbs file and seeing the old output means the compiled artifact wasn't invalidated. php artisan view:clear only knows about Blade; make sure your deploy or local workflow clears the handlebars compile directory too, or enable runtime recompilation in local only.
  • Flags are a feature dial, and less is more. The default bitmask is conservative. If {{#each}} over an object misbehaves, or {{this}} renders [object Object], or a helper's options.fn() returns nothing, you're usually missing a LightnCandy flag rather than hitting a package bug. Start with FLAG_ERROR_EXCEPTION so compile failures throw instead of silently rendering empty strings.
  • Escaping is {{ }} vs {{{ }}}. Double braces HTML-escape (&, <, >, quotes); triple braces are raw. The most common XSS in Handlebars apps is a triple-stache around user input. If you need no escaping globally, that's FLAG_NOESCAPE — use it only for non-HTML output like plain-text emails or config generation.
  • Helper signatures are Handlebars-shaped, not Laravel-shaped. A block helper receives ($args..., $options) where $options carries fn, inverse, hash, data. Returning a string from a block helper is different from echoing it. Getting $options['fn']($this) vs $options['inverse']($this) backwards produces silently blank blocks.
  • Partials resolve at compile time. A partial that doesn't exist fails during compilation, often with an unhelpful path. If you need to choose partials dynamically at runtime, that requires an explicit runtime-partial flag; don't assume it works.
  • No Blade, ever. @include, @csrf, {{ $var }} all silently do nothing or render literally. If you need CSRF tokens in a form, pass them in as data.
  • Data must be arrays or scalars. LightnCandy expects plain PHP arrays for {{#each}} and nested lookups. Objects work partially and inconsistently; convert at the boundary.
  • Debugging a compile failure. Temporarily point the compile path at a scratch directory and inspect the generated PHP — it's readable and usually shows exactly which expression failed. LightnCandy also has an AST dump mode that's far more informative than the exception message.
  • Vendor risk is real. 38 stars, one maintainer, and a thin wrapper over a third-party compiler. Pin the version in composer.json, check the Laravel constraint against your version before upgrading Laravel, and keep the abstraction thin — a single Renderer wrapper class means you can replace this with a direct LightnCandy call (or a different engine) in an afternoon.
  • It's a niche tool with a clear niche. If your templates are Blade-only and never shared with JavaScript, this package adds a compiler, a cache directory, and a second syntax for no benefit. If you are sharing templates with a frontend Handlebars build, it removes a whole class of "the email looks different from the app" bugs — that's the trade, and it's a good one.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin