Pug 3.0 Removes Configurable `interpolate` Option: Impact on Template Delimiters and Escaping
0 reputation · 01 Sept 2024, 19:51 UTC
0 reputation · 01 Sept 2024, 19:51 UTC
Goal: Assess whether existing Pug 2.x projects that relied on the configurable interpolate option can be used unchanged with Pug 3.0.
Constraint: In Pug 3.0 the interpolate option has been removed; templates now use the fixed #{} delimiter and default HTML5‑safe escaping. Legacy code that set a custom delimiter or disabled escaping via this option may encounter syntax errors or unexpected double‑escaping.
Uncertainty: It is unclear what migration path preserves the original rendering behavior without rewriting every template, especially for mixins that injected raw HTML or for projects that depended on turning off auto‑escaping.
interpolate value?interpolate option?29275 reputation · 02 Sept 2024, 01:25 UTC
There is no supported way to configure interpolation delimiters or disable auto-escaping globally in current Pug, and there is no compatibility flag or shim to restore that behavior. However, one part of the premise needs checking: the removal of any configurable interpolate option appears to predate Pug 3.0 — it traces back to the Jade-to-Pug lineage, around the 2.x era. Before blaming a 3.0 upgrade, verify against the changelog for your exact versions, because the migration work is identical either way.
In current Pug, the lexer treats interpolation tokens as hard-coded:
#{expression} — interpolate with HTML escaping (the safe default).!{expression} — interpolate without escaping (raw HTML injection).\#{...} — emit a literal #{...} in the output, useful when templates feed client-side frameworks that use similar syntax.Escaping is not controlled by a global option; it is selected per interpolation site by choosing #{} versus !{}. Options like doctype remain configurable — do not confuse those with delimiter behavior.
Custom delimiters. If templates used a non-standard delimiter from a very old Jade setup, the only path is a mechanical rewrite to #{}/!{}. Because the syntax is unambiguous, a scripted search-and-replace across .pug files, followed by a full render diff, is usually sufficient.
Raw-HTML injection. Replace any pattern that relied on globally disabled escaping with explicit !{} at each injection site. This is more verbose but safer: escaping stays on everywhere except where you deliberately opt out. Never apply !{} to user-controlled data — that is a direct XSS vector. If mixins previously emitted raw markup, pass the HTML in via a variable and render it with !{} at the call site, keeping the trust decision visible.
Confirm behavior on your installed version rather than trusting any changelog summary:
node -e "const p=require('pug');console.log(p.render('p #{x} | !{x} | \\#{x}',{x:'hi'}))"
Expected output escapes the first interpolation, passes the second through raw, and prints the third literally. You can also grep node_modules/pug-lexer for the interpolation token handling to confirm the delimiters are fixed in your version. Finally, render your full template suite under both the old and new Pug versions and diff the HTML — this catches delimiter and escaping regressions that unit tests miss.
If you can share the exact Pug versions you are moving between (e.g. 2.0.4 → 3.0.2), the specific changelog entries can be confirmed; the rewrite guidance above applies regardless.
Use comments to ask for clarification. Post a solution as an answer.
2,100 reputation · 02 Sept 2024, 03:46 UTC
Although Pug 3.0 no longer accepts a global interpolate option, you can still emit raw HTML from a variable by using the unescaped interpolation syntax !{variable}. If you need to output the literal characters #{ without triggering interpolation, escape the leading hash with a backslash: \#{.... This per‑site control replaces the former global flag, so any mixin that previously relied on disabled escaping must be updated to place !{} at each injection point, which also makes potential XSS sources explicit in the template.