Pug Template Inheritance: Building Maintainable Layouts with extends and block
Centralize your HTML skeleton in one Pug layout and let child templates override named blocks — including append/prepend for additive changes and multi-level section layouts.
01 Sept 2026, 15:50 UTC

The problem: duplicated page skeletons
Every page in a site shares the same skeleton — the <html> element, the <head> with meta tags and stylesheets, a header, a footer. Copying that skeleton into every template means a change to the footer becomes a change to every file. Pug's template inheritance solves this: one parent layout defines the skeleton with named block placeholders, and each child template uses extends to inherit the skeleton and fill in only what differs.
This guide builds a two-level layout: a base layout, a section layout that extends it, and a page that extends the section layout. It assumes Pug 3.x and Node.js 18 or later.
Prerequisites
- Node.js installed, with a project directory containing
npm install pug(orpug-cliglobally if you prefer compiling from the shell). - Basic familiarity with Pug's indentation-based syntax — indentation is significant, and mixing tabs and spaces in one file causes compile errors.
Step 1: Define the base layout with blocks
Create views/layout.pug. A block is a named slot; any default content you put inside it is used only when a child does not override it.
doctype html
html(lang="en")
head
block title
title My Site
block head
link(rel="stylesheet", href="/css/main.css")
body
header
nav Site navigation here
main
block content
p Default content if a child provides none.
footer
p © 2026 My SiteThree blocks are defined: title, head, and content. Blocks can be nested — a block inside content would let children control a sub-region, such as a sidebar, without touching the rest of the page.
Step 2: Extend the layout from a child template
Create views/page.pug:
extends layout.pug
block title
title About Us — My Site
block content
h1 About Us
p This page overrides only the content block.Two rules matter here:
extendsmust be the first non-comment line in the child file. Anything placed before it (other than comments) breaks inheritance.- Content outside a block is silently ignored in a child that extends a layout. If you write a stray
p Helloat the top level ofpage.pug, it will not appear in the output and Pug will not warn you. This is the most common inheritance mistake.
Step 3: Append instead of replace
Sometimes a child should add to a block rather than replace its default — for example, adding a page-specific stylesheet while keeping the shared one. Use block append (or the shorthand append):
extends layout.pug
block append head
link(rel="stylesheet", href="/css/about.css")
block content
h1 About UsThe rendered <head> now contains both main.css (from the parent's default) and about.css. The counterpart block prepend inserts content before the parent's default. Note that append/prepend only make sense when the parent block has default content; otherwise they behave like a normal override.
Step 4: Compile and verify
From the project root, with pug-cli installed, run:
npx pug views/page.pug --out distThis writes dist/page.html. Open it and check three things:
- The
<title>reads "About Us — My Site", not the layout default. - Both stylesheets appear in
<head>. - The header and footer from
layout.pugsurround the child's<h1>.
If you render through Express instead (app.set('view engine', 'pug') then res.render('page')), the same checks apply to the response body — fetch the page and inspect the HTML.
Multi-level inheritance: a section layout
Inheritance chains more than two levels deep work the same way. A section layout can extend the base layout and define new blocks for its own children:
// views/docs-layout.pug
extends layout.pug
block content
.docs-wrapper
aside
block sidebar
p Default sidebar
article
block articleA docs page then extends docs-layout.pug and fills sidebar and article. Keep chains shallow — two or three levels is manageable; deeper nesting makes it hard to trace where a piece of output originates.
Limitations and failure modes
- Circular inheritance fails to compile. If A extends B and B extends A, Pug throws an error at compile time. Keep the hierarchy a strict tree.
- Silent content loss. As noted, top-level content in a child is discarded. If rendered output is missing markup you wrote, check that it sits inside a
block. - Path resolution. The path in
extendsis relative to the current file unless your integration defines abasediroption, in which case absolute-from-basedir paths likeextends /layout.pugwork. A "file not found" compile error usually means a wrong relative path. - Variables from the child are visible in the layout only if declared before the
extendsline is not possible — instead, declare shared variables withblock-level logic or pass them at render time. Data passed tores.renderor the compiled function is available in both parent and child.
No rollback is needed here — adding or editing templates changes no runtime state; revert edits in version control if a layout change breaks pages, and recompile to confirm.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.