bkwld/laravel-pug
Adds Pug (formerly Jade) template support to Laravel. Register the .pug extension and write views with Pug's concise, indentation-based syntax—mixins, includes, and layouts included—while Laravel handles rendering and caching. Works alongside Blade templates.
Install
composer require bkwld/laravel-pug
Laravel 5.5+ auto-discovers Bkwld\LaravelPug\ServiceProvider. On older Laravel versions (or Lumen), register the provider manually in config/app.php / bootstrap/app.php.
Publish the config (recommended)
php artisan vendor:publish --provider="Bkwld\LaravelPug\ServiceProvider"
This creates config/laravel-pug.php. The options in this file are passed through to the underlying Phug renderer, so this is the first place to look when you need to change caching, pretty-printing, extensions, or include paths.
First view
Create resources/views/hello.pug:
doctype html
html
head
title Hello
body
h1 Hello #{name}
Route/controller as usual:
Route::get('/', fn () => view('hello', ['name' => 'World']));
The package registers the .pug extension with Laravel's view finder, so view('hello') resolves hello.pug exactly like hello.blade.php would.
First real use case
Pick the smallest page in your app (a static marketing page, error page, or auth screen) and port it first. You will immediately exercise layout inheritance, data passing, and the compiled-view cache — the three areas that cause the most friction.
Where to look first
config/laravel-pug.php — renderer options.storage/framework/views — compiled Pug output (this is what PHP actually executes).phug/phug.Layouts and sections
Use Pug's native inheritance rather than trying to emulate Blade:
//- resources/views/layouts/app.pug
doctype html
html
head
title= title ?? 'App'
link(rel="stylesheet" href=asset('css/app.css'))
body
block content
//- resources/views/dashboard.pug
extends layouts/app
block content
h1 Dashboard
p Welcome back, #{user.name}
Partials and components
include partials/nav
include partials/footer
For repeated markup, mixins are the closest thing to Blade components:
//- resources/views/mixins/card.pug
mixin card(title)
.card
.card__title= title
.card__body
block
include mixins/card
+card('Stats')
p 42 active users
Passing data and iterating
Data flow is identical to Blade — view('posts.index', compact('posts')) — but the template syntax is different:
each post in posts
article
h2= post.title
p= post.excerpt
else
p No posts yet.
if user.isAdmin
a(href=route('admin.dashboard')) Admin
Calling Laravel helpers
Phug expressions are PHP, so framework helpers work directly inside attributes and output expressions:
a(href=route('profile', ['user' => $user->id])) Profile
img(src=asset('img/logo.svg') alt="Logo")
For HTML-producing helpers (csrf_field(), method_field()), use the unescaped operator:
form(method="POST" action=route('posts.store'))
!= csrf_field()
input(type="text" name="title")
Compiled-view caching
Compiled templates land in storage/framework/views, and Laravel's normal view cache applies. In local development, edits are recompiled on demand. In production, deploy with:
php artisan view:cache
which precompiles both Blade and Pug views.
Front-end asset workflow
bkwld/laravel-pug only handles server-side rendering. If you also want .pug sources compiled for the front end, pair it with a Mix/Webpack Pug plugin; keep the two pipelines' include paths aligned so partials resolve in both.
Allow Phug 2 (2.5.0) is additive, not breaking
Release 2.5.0 only broadens the Composer constraint on the underlying engine to permit phug/phug v2 as well as the previous line. Existing installs are unaffected unless you explicitly upgrade Phug. Two practical consequences:
Pin phug/phug in your composer.json if you need deterministic output across deploys.
View name resolution surprises
view('home') prefers whichever extension Laravel finds first. If both home.blade.php and home.pug exist, you can get the wrong one. During migrations, rename or delete the old Blade file rather than leaving both.
Blade directives do not exist
@extends, @include, @csrf, @auth, {{ }} — none of these work. Use extends, include, != csrf_field(), if (auth()->check()), and #{} / !{} respectively. A grep for @ and {{ in converted templates catches leftover Blade syntax quickly.
Caching bites in three places
php artisan view:clearphp artisan config:cache after editing config/laravel-pug.phpphp artisan optimize:clearIf a Pug change is not appearing, clear the view cache before debugging anything else.
Escaping is a real XSS surface
#{value} escapes; !{value} and != do not. It is easy to reflexively use != for helpers and then reuse the pattern for user data. Audit every unescaped output — especially in comments, profiles, and anywhere users control text.
Debugging is one level removed
Runtime errors point at the compiled PHP file in storage/framework/views, not the .pug source. Open the compiled file to map the failing line back, or enable pretty output in the config so the generated markup is readable. Phug's own error pages are more informative than Laravel's generic view exception, so check the compiled file before assuming a data problem.
Include paths and namespaced views
include partials/nav resolves relative to the configured view paths. Package views (vendor::foo) need the package's namespace registered as a view namespace as usual; Pug includes do not automatically inherit Laravel's namespace resolution, so prefer explicit paths for cross-package partials.
Performance
Compiled templates are plain PHP, so steady-state rendering is fast. The cost is compile time on cache misses and the memory overhead of the Phug renderer. If a hot endpoint feels slow in local dev, verify you are hitting the compiled cache rather than recompiling per request.
Extension points
Custom Pug keywords, filters, and extensions are configured through the renderer options in config/laravel-pug.php. This is the right place to add project-specific shorthands (e.g. a money filter), but treat them as application code: document them and make sure the Phug version constraint you pin supports the APIs you use.
How can I help you explore Laravel packages today?