NodeJSPug

Pug

Pug (formerly Jade) takes the opposite approach to EJS: instead of HTML with embedded code, you write a terse, indentation-based syntax with no angle brackets or closing tags. The structure of the document comes from whitespace, like Python. Fans love how little they have to type; the cost is a real syntax to learn and the discipline indentation demands. This page covers the syntax, interpolation, control flow, and template inheritance.

Setup

Bash
npm install pug

JS
app.set('view engine', 'pug')
app.set('views', './views')

app.get('/', (req, res) => {
  res.render('index', { title: 'Home', user: req.user })  // views/index.pug
})
HTML without the brackets

The same markup, two ways

Text
// HTML:
<div class="card">
  <h1 id="title">Hello</h1>
  <p>Welcome back</p>
</div>

// Pug — tag name, then nesting by indentation:
.card                       // div is implied; .card = class="card"
  h1#title Hello            // #title = id="title"
  p Welcome back
Indentation IS the structure — no closing tags
In Pug, a tag's children are whatever is indented beneath it; the dedent closes it. There are no `</div>` tags to balance. `div` is implied when you start with a `.class` or `#id`, so `.card` means `<div class="card">`. This is dramatically less typing than HTML — but the trade-off is that **indentation is significant**: get it wrong and the document nests incorrectly.
Attributes, text, and interpolation

Text
a(href="/users/" + user.id, class="link") View profile

h1= user.name              //- "=" outputs an ESCAPED expression
p Hello #{user.name}!      //- #{ } interpolates (escaped) inside text
p!= trustedHtml            //- "!=" and !{ } output RAW (unescaped) HTML

input(type="checkbox", checked=user.active)   //- boolean attribute
`=`/`#{}` escape; `!=`/`!{}` do NOT — same XSS rule as every engine
Pug escapes by default: `= expr`, `#{expr}` in text, and attribute values are all HTML-escaped. The buffered-but-**unescaped** forms are `!= expr` and `!{expr}` — use them only for HTML you generated and trust. As with [EJS's `<%-`](/nodejs/ejs), reaching for the unescaped form to render user content opens an [XSS](/nodejs/cors) hole. Default to escaped everywhere.
Conditionals and loops

Text
if user.isAdmin
  a(href="/admin") Admin panel
else
  p Standard account

ul
  each order in user.orders
    li= order.title + " — $" + order.total.toFixed(2)
  else
    li No orders yet           //- 'each ... else' runs when the array is empty

//- A plain JS line (unbuffered code) starts with a hyphen:
- const count = user.orders.length
p You have #{count} orders
`each ... else`, `if/else`, and `-` for raw JS
Pug has built-in iteration (`each item in list`) with a handy `else` branch for empty collections, plus `if`/`else if`/`else` and `case`/`when`. A line beginning with `-` is unbuffered JavaScript (assignments, logic that produces no output); `=` is buffered (outputs the value). The control flow is concise, but it's Pug's own syntax — not raw JS blocks like EJS.
Template inheritance — layouts done right

views/layout.pug

Text
doctype html
html
  head
    title= title
    block head            //- child pages can add to this block
  body
    include partials/nav  //- shared navigation
    block content         //- child pages REPLACE this block
    include partials/footer

views/index.pug — extends the layout

Text
extends layout

block content
  h1 Welcome
  p This fills the 'content' block of the layout.

block append head
  link(rel="stylesheet", href="/home.css")   //- adds to the head block
`extends` + `block` is Pug's standout feature
Pug's **template inheritance** is cleaner than ad-hoc includes: a layout defines named `block`s, and child templates `extends` it and fill those blocks. You can also `append`/`prepend` to a block (e.g. add a page-specific stylesheet to `head`). This gives real layout composition out of the box — something EJS approximates with includes but doesn't formalize.
Mixins — reusable components

Text
//- Define a parameterized fragment:
mixin card(title, body)
  .card
    h3= title
    p= body

//- Use it like a function:
+card("Hello", "World")
+card(post.title, post.excerpt)
Mixins are Pug's functions for markup
A `mixin` is a named, parameterized chunk of template you invoke with `+name(args)` — the equivalent of a component or a parameterized partial. Mixins keep repeated UI (cards, form fields, buttons) defined once. Combined with inheritance, they make Pug genuinely composable.
The trade-off

Pro

Con

Very concise — far less typing

A whole new syntax to learn

Real inheritance + mixins

Whitespace-sensitive; misindentation breaks layout

Forces clean structure

Pasting existing HTML requires conversion

Great for hand-authored pages

Designers/tools expect plain HTML (EJS wins there)

You can't paste HTML into a Pug file
Because Pug isn't HTML, you can't drop a snippet from a design tool, Bootstrap, or Stack Overflow straight into a `.pug` file — it must be converted to Pug syntax first. If your workflow involves lots of existing HTML or non-Pug-savvy collaborators, that friction is real. Choose Pug when the team is comfortable with it and values terseness; choose [EJS](/nodejs/ejs)/[Handlebars](/nodejs/handlebars) when staying close to HTML matters.
Next
A logic-less engine that keeps templates deliberately simple: [Handlebars](/nodejs/handlebars).