Swig

Multi-flavor template engine for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired) and Twig syntax via dedicated frontends sharing one IR backend. gina-io/swig started as a maintained continuation of the abandoned paularmstrong/swig (last released 2014) and is now a standalone project. Security and bug fixes ship here.
Part of the Gina ecosystem. This is the built-in template engine for Gina (npm), a Node.js MVC framework with HTTP/2, multi-bundle architecture, and scope-based data isolation.
Swig is a Jinja2/Django-inspired template engine for node.js and browsers. The syntax will feel familiar to Jinja2 and Django users, but Swig is not drop-in compatible with either — porting templates from an existing project requires a handful of changes. See the Migration Guide for the full parity list and workaround patterns.
Coming from Twig? Install @rhinostone/swig-twig instead — a dedicated Twig-syntax frontend with closer parity than working around incompatibilities here.
Workspace packages
@rhinostone/swig | Native Swig syntax (Jinja2/Django-inspired). Drop-in for @rhinostone/swig@1.x consumers. | Upgrading from @rhinostone/swig@1.x, or starting fresh with Swig syntax. |
@rhinostone/swig-twig | Twig-syntax frontend with closer Twig parity. | Migrating from PHP Twig, or writing new templates in Twig syntax. |
@rhinostone/swig-core | Shared IR, backend, and runtime primitives. | Building a custom flavor frontend. Otherwise pulled in transitively. |
Each frontend pins the matching @rhinostone/swig-core version exactly during the alpha cycle — the IR is not stable across alpha minors. From 2.0.0 stable onward, frontends and the core release in lockstep.
Features
- Available for node.js and major web browsers.
- Express compatible.
- Object-Oriented template inheritance.
- Apply filters and transformations to output in your templates.
- Hardened against prototype-pollution —
__proto__ / constructor / prototype blocked at parser, tag-side, and IR-emission layers. CVE-2023-25345 fully patched. 9 regression cases under tests/regressions.test.js.
- Automatically escapes all variable output (HTML by default; configurable per-call).
- Lots of iteration and conditionals supported.
- Robust without the bloat.
- Extendable and customizable — register custom filters, tags, and loaders per-instance.
Benchmarks
benchmarks/render.js measures sync-render throughput across five workload shapes against Nunjucks.
cd benchmarks && npm install && node render.js
In production-typical settings (autoescape on), @rhinostone/swig outperforms Nunjucks on iteration-heavy templates by 2–3.5× and ties on simple control flow. See benchmarks/README.md for the methodology, the full result table, and how to reproduce on your own hardware.
- File an issue at gina-io/swig/issues.
- Swig v0.x → v1.x migration notes — the original upstream wiki has been deleted; see
HISTORY.md entries around v1.0.0 for the individual breaking changes. For porting from Jinja2 or Django into Swig, see the Migration Guide.
Installation
npm install @rhinostone/swig
For Twig syntax:
npm install @rhinostone/swig-twig
Documentation
User-facing documentation lives in the Gina Docusaurus site under the Swig Template Engine section, maintained in gina-io/docs at docs/swig/. The JSDoc blocks in lib/swig.js, lib/filters.js, lib/tags/, and lib/loaders/ remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
Basic Example
Template code
<h1>{{ pagename|title }}</h1>
<ul>
{% for author in authors %}
<li{% if loop.first %} class="first"{% endif %}>{{ author }}</li>
{% endfor %}
</ul>
node.js code
var swig = require('@rhinostone/swig');
var template = swig.compileFile('/absolute/path/to/template.html');
var output = template({
pagename: 'awesome people',
authors: ['Paul', 'Jim', 'Jane']
});
Output
<h1>Awesome People</h1>
<ul>
<li class="first">Paul</li>
<li>Jim</li>
<li>Jane</li>
</ul>
For working example see examples/basic.
Migrating from @rhinostone/swig@1.x
@rhinostone/swig@2.x is drop-in for 1.x consumers — swig.compileFile, swig.renderFile, swig.setFilter, swig.setTag, and the rest of the public API are unchanged. The internal carve into @rhinostone/swig-core is transparent (test gate during the alpha cycle: byte-identical compiled output against the 1.x test suite).
2.0.0 also ships @rhinostone/swig-twig, a sibling Twig-syntax frontend. Switching is opt-in — your existing @rhinostone/swig install keeps working.
Migrating from Jinja2 or Django
Swig is inspired by Jinja2 and Django, not a drop-in replacement. Common pitfalls when porting existing templates:
- No
is / is not / not in operators — rewrite {% if x is defined %} as {% if x !== undefined %}, {% if x not in xs %} as {% if not (x in xs) %}.
- Django
forloop.counter → Swig loop.index (Swig follows Jinja2 loop-variable naming).
{{ super() }} / {{ block.super }} → {% parent %} — Swig uses a dedicated tag inside the overriding block.
- Django filter args use a colon (
|date:"Y-m-d") — Swig uses parens (|date("Y-m-d")).
{% with x=1 %} → {% set x = 1 %}, and no block-form {% set %}…{% endset %}.
- No
{% from "f" import x %} — use {% import "f" as ns %} + ns.x instead.
- Method calls require parens — Django auto-invokes
x.get_absolute_url; Swig needs x.get_absolute_url().
- ~25 Jinja2 filters are absent —
default, truncate, tojson, round, int, float, map, select, batch, trim, etc. Register them via swig.setFilter(name, fn).
Full parity tables and workaround patterns: Migration Guide.
How it works
Swig reads template files and translates them into cached JavaScript functions. The pipeline is: parse → emit IR → lower IR to JS source → new Function(...). At render time, the compiled function runs against a context object to produce the output string.
In 2.x, frontend parsers (native Swig syntax in @rhinostone/swig, Twig syntax in @rhinostone/swig-twig) emit a shared intermediate representation. The backend in @rhinostone/swig-core lowers IR to JS. New flavors plug in at the frontend without touching the runtime.
License
MIT. Copyright (c) 2010-2016 Paul Armstrong and contributors, (c) 2026 Rhinostone. See LICENSE for the full text and AUTHORS for the contributor roster.