Multi-flavor template engine for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired), Twig syntax, Python Jinja2 syntax, and Django Template Language 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.
Coming from Python Jinja2? Install @rhinostone/swig-jinja2 — a dedicated Jinja2-syntax frontend (near-subset) with closer parity than porting to native Swig syntax here.
Coming from Django? Install @rhinostone/swig-django — a dedicated Django Template Language frontend that renders real Django templates, with far closer parity than native Swig syntax here.
| Package | Description | When to use |
|---|---|---|
@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-jinja2 |
Python Jinja2-syntax frontend (near-subset). | Migrating from Python Jinja2, or writing new templates in Jinja2 syntax. |
@rhinostone/swig-django |
Django Template Language frontend (real DTL). | Migrating from Django, or writing new templates in Django 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 (no caret, no tilde) — frontends and the core release in lockstep on every cut.
- 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/prototypeblocked at parser, tag-side, and IR-emission layers. CVE-2023-25345 fully patched. 11 CVE regression cases undertests/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/render.js measures sync-render throughput across five workload shapes against Nunjucks.
cd benchmarks && npm install && node render.jsIn 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.mdentries around v1.0.0 for the individual breaking changes. For porting from Jinja2 or Django into Swig, see the Migration Guide.
npm install @rhinostone/swig
For Twig syntax:
npm install @rhinostone/swig-twig
For Python Jinja2 syntax:
npm install @rhinostone/swig-jinja2
For Django syntax:
npm install @rhinostone/swig-django
User-facing documentation lives in the Gina Docusaurus site under the Swig Template Engine section, maintained in gina-io/docs at docs/templating/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.
<h1>{{ pagename|title }}</h1>
<ul>
{% for author in authors %}
<li{% if loop.first %} class="first"{% endif %}>{{ author }}</li>
{% endfor %}
</ul>var swig = require('@rhinostone/swig');
var template = swig.compileFile('/absolute/path/to/template.html');
var output = template({
pagename: 'awesome people',
authors: ['Paul', 'Jim', 'Jane']
});<h1>Awesome People</h1>
<ul>
<li class="first">Paul</li>
<li>Jim</li>
<li>Jane</li>
</ul>For working example see examples/basic.
@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; 2.5.0 adds @rhinostone/swig-jinja2 for Python Jinja2 syntax; and 2.7.0 adds @rhinostone/swig-django for Django Template Language syntax. Switching is opt-in — your existing @rhinostone/swig install keeps working.
Swig is inspired by Jinja2 and Django, not a drop-in replacement. Common pitfalls when porting existing templates:
- No
is/is not/not inoperators — rewrite{% if x is defined %}as{% if x !== undefined %},{% if x not in xs %}as{% if not (x in xs) %}. - Django
forloop.counter→ Swigloop.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.xinstead. - Method calls require parens — Django auto-invokes
x.get_absolute_url; Swig needsx.get_absolute_url(). - ~25 Jinja2 filters are absent —
default,truncate,tojson,round,int,float,map,select,batch,trim, etc. Register them viaswig.setFilter(name, fn).
Full parity tables and workaround patterns: Migration Guide.
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, Python Jinja2 syntax in @rhinostone/swig-jinja2, Django Template Language syntax in @rhinostone/swig-django) 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.
MIT. Copyright (c) 2010-2016 Paul Armstrong and contributors, (c) 2026 Rhinostone. See LICENSE for the full text and AUTHORS for the contributor roster.