Squirrelly is a small template engine with a different tag syntax
Semi-embedded JS template engine that supports helpers, filters, partials, and template inheritance. 4KB minzipped, written in TypeScript ⛺
At a glance
- What is it?
- Squirrelly is a JavaScript template engine of about 4KB gzipped with zero runtime dependencies, offering helpers, filters, partials and template inheritance alongside conditionals and loops. It works with Express out of the box, and it replaces EJS-like syntax with @if and @each tags that will not port back line for line.
- Who is it for?
- Adopt Squirrelly if you run an Express application, want a template engine with helpers, filters, partials and inheritance that adds about 4KB gzipped, and are willing to adopt the @if and @each syntax rather than keep an EJS mental model.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 128 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A template engine sized for the Express render path
Squirrelly is a JavaScript template engine that works out of the box with ExpressJS, and the README states that the full version weighs about 4KB gzipped with zero dependencies. Both numbers matter for the audience: a server that renders a page per request can justify a large engine, but a microservice that renders one small view per call often cannot, and a four kilobyte engine with an empty dependency tree is a defensible answer to that constraint.
The feature set is broader than the size suggests. The list covers helpers and filters, conditionals, loops, custom delimiters, comments, caching, precompilation, inline JavaScript, partials, template inheritance, whitespace control, filter parameters, and async support for filters, helpers and partials. The README frames version 8 and later as a rewrite that added filter parameters, whitespace control, partials and template inheritance, with the stated goal of bringing the power of Nunjucks, the simplicity of EJS and the small bundle size of the earlier versions.
The error reporting claim is worth taking at face value as a design goal rather than a promise. Template engines live or die on what a stack trace looks like when a template is wrong, and a project that lists good error reporting as a headline feature is telling you what it optimised for.
One item on the list is a good demonstration of the syntax choices. The README notes that a value followed by a quoted string inside a template expression compiles correctly in Squirrelly, where it fails with DoT or EJS. That is a narrow difference, but it is the kind of thing that decides whether an engine handles a real codebase.
The it context and the @ prefixed tags
The syntax is the main thing to understand before anything else. Data lives under it, and the control-flow tags are prefixed with an at sign rather than written as angle-bracket or percent-sign blocks.
A condition looks like this:
{{@if(it.somevalue === 1)}}
Display this
{{#else}}
Display this
{{/if}}and a loop looks like this:
{{@each(it.somearray) => val, index}}
Display this
The current array element is {{val}}
The current index is {{index}}
{{/each}}The each tag takes an arrow form, so the iteration variable and the index are named at the tag rather than through a nested block, and the values inside the body are referenced directly as val and index rather than through it. The else branch is marked with a hash, {{#else}}, which is a different convention from the at prefixed tags around it.
Once the syntax is familiar, the object model is small. A template string plus a data object is all that is required to render, and the example in the README shows the whole call:
var myTemplate = "<p>My favorite kind of cake is: {{it.favoriteCake}}</p>"
Sqrl.render(myTemplate, {favoriteCake: 'Chocolate!'})
// Returns: '<p>My favorite kind of cake is: Chocolate!</p>Interpolated values are read from it, so a template author who is used to EJS locals will need to adjust, and a template author who is used to Pug will find the tag shape familiar. Custom delimiters are supported, which is the escape hatch for templates that contain JavaScript-style braces of their own.
Partials, inheritance and helpers in the same engine
The v8 rewrite added the three features that separate a small template engine from a toy. Partials let one template include another, which is how a page splits into header, body and footer without a build step that concatenates files. Template inheritance gives a base layout that child templates fill, which is the layout mechanism Pug and Nunjucks users expect. Helpers and filters give the template language reusable functions, and filters take parameters in this version, which is a small change with real consequences for reusable formatting.
Async support runs through all three. Async filters, async helpers and async partials mean a template can await data as it renders, so a partial that loads a fragment from a slow service does not force a second round of request handling. For an Express app, that removes a class of two-pass rendering code.
Caching and precompilation are listed as well, and they are the two features that matter for throughput. Caching keeps compiled templates around between requests, and precompilation moves compilation out of the request path entirely, into a build step. The repository layout supports that story: there is a rollup.config.ts at the root, a src directory for the TypeScript sources, a test directory, a browser-tests directory, an examples directory, and a distribution.md file that sits alongside the README.
The examples directory is worth a look before adopting. It contains conditionals.ts, filters.ts, trycatch.ts and runkit.js, which is also the file the package points RunKit at through the runkitExampleFilename field in package.json. The examples show the error handling case, and the package itself points at a public playground at squirrelly.js.org for trying syntax without a local project.
Three module formats, sideEffects false, and a 4KB claim
The package.json entry points tell you how the engine is meant to be consumed. The main field points at dist/squirrelly.cjs.js for CommonJS, the module field points at dist/squirrelly.es.js for ES modules, and the browser and jsdelivr fields point at a minified browser bundle at dist/browser/squirrelly.min.js. The typings field points at dist/types/index.d.ts, so a TypeScript project gets types without a separate @types package. The README lists exporting both ES modules and UMD as a feature, and the distribution file set in the manifest is dist and examples, so the published tarball stays small.
The sideEffects field is set to false. That is a meaningful flag for bundlers: it tells the tree shaking machinery that importing the module has no side effects, so unused exports can be dropped from a production bundle. For a library whose selling point is a small weight, that flag is what lets a bundler honour the promise instead of pulling the whole engine in.
The build itself is rollup, and the build script also runs typedoc to generate API documentation into a docs directory, with a separate deploy-docs script that publishes to GitHub Pages through a ts-node tool. Tests run with jest under coverage, and there is a test:prod script that lints first and then runs the tests with the cache disabled.
The 4KB figure should be read as a gzipped bundle size claim for the full version, which the README states directly. The README also says Squirrelly has been benchmarked against Marko, Pug, doT, Swig, Handlebars, Mustache and Nunjucks, and that in each test Squirrelly was fastest. That is the project's own benchmark against engines with different feature sets, so treat the ranking as directional rather than a reason to switch on its own.
The README says version 8, the package is at 9.1.1
The most concrete thing to know about this project right now is a mismatch. The README's summary still says the current line is version 8 and describes that rewrite, and it points readers to a blog post about version 8 changes. The package version in package.json is 9.1.1, and the release history shows v9.0.0 on 2023-05-13, v9.1.0 on 2024-07-02 and v9.1.1 on 2026-06-16.
So the README describes an architecture that is two major versions behind the published package, and the changes between them are not described there. The documentation site is the place to look for what version 9 changed, and this is the practical argument for reading the docs online rather than relying on the repository page.
Release cadence tells its own story. v9.0.0 and v9.1.0 are more than two years apart, and v9.1.1 arrived on 2026-06-16, the same day as the last push to the repository. The project is not dormant, but it is not on a frequent release train either. There is also a versioning wrinkle worth knowing: the README states that Squirrelly v7 will continue to be maintained and can be found on a v7 branch, and a separate v7 branch being maintained alongside the current line means a team can end up on two different major versions of the same engine.
The licence is MIT, which is the permissive standard and comes with no runtime cost.
What the syntax change costs, and when to use something else
The honest case against Squirrelly is the porting cost. If an application already has templates written for EJS, Pug or Handlebars, moving to Squirrelly is not a settings change. The it context, the at prefixed tags, the arrow form of each and the hash prefixed else branch all have to be rewritten template by template, and the project itself positions it as an alternative to EJS rather than a drop in.
The second limitation is documentation placement. The README states plainly that nobody reads long documentation in a README, and points to squirrelly.js.org. That is a reasonable call, but it means the repository is not self-contained: if the site is unreachable, or you are offline, or you are reading a vendored copy, the detailed reference for filters and inheritance is not in front of you.
The third is the environment metadata. The engines field in package.json asks for node >=6.0.0, which is a very old floor for a TypeScript source tree that builds with a modern toolchain and ships ES modules. Treat that field as stale metadata rather than a compatibility promise.
On alternatives, the README points at Eta, calling it its cousin and describing it as a lightweight, faster and more reliable alternative to EJS. Eta is the closest sibling in approach, a small template engine, and if your requirement is template rendering with minimal weight rather than the specific feature set of Squirrelly, that is the comparison the project itself invites. The other direction, Nunjucks or Pug, trades bundle size for a larger feature surface, and Squirrelly's own framing of its v8 rewrite was to bring Nunjucks-like power with EJS-like simplicity, which is exactly the trade you are making by choosing it.
Editorial conclusion
Adopt Squirrelly if you run an Express application, want a template engine with helpers, filters, partials and inheritance that adds about 4KB gzipped, and are willing to adopt the @if and @each syntax rather than keep an EJS mental model. Do not adopt it if your team already has a large EJS or Pug template estate to port, if you need autoescaping behaviour documented to a security standard, or if you want documentation inside the repository, since the long form docs live on the separate squirrelly.js.org site. Before installing, check the current version yourself, because the README still describes version 8 while the published package is 9.1.1 from 2026-06-16, and read the node engines field in package.json, which claims node >=6.0.0 for a TypeScript build that ships ES modules.
Frequently asked questions
How big is the squirrelly template engine?
The README states that the full version weighs about 4KB gzipped, and the feature list includes zero dependencies. The package ships CommonJS, ES module and browser builds, with typings included.
How do I write a loop in Squirrelly templates?
Loops use the at prefixed each tag with an arrow form, for example `{{@each(it.somearray) => val, index}}` and a closing `{{/each}}`, with the element and index referenced directly inside the body.
Does Squirrelly work with ExpressJS?
Yes. The README says it works out of the box with ExpressJS, and the repository topics list it as an ExpressJS templating engine.
What is the current version of squirrelly?
The package version in package.json is 9.1.1, released on 2026-06-16, and the last push to the repository was on the same date. The README still describes the version 8 rewrite, so the site at squirrelly.js.org is the better source for version 9 details.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bgub-squirrelly)Was this useful?