LiquidJS ships four bundles and a performance check in its own test script
A simple, expressive, safe and Shopify compatible template engine in pure JavaScript.
At a glance
- What is it?
- LiquidJS is a pure JavaScript implementation of the Liquid template language used by Shopify, Jekyll and GitHub Pages, running on Node.js, in browsers and from the command line. It is currently at 10.30.0, and its build and test scripts treat a performance regression as a build failure rather than an observation.
- Who is it for?
- Adopt LiquidJS when your templates must stay Liquid compatible while running somewhere other than Ruby, whether that is a Node service, a static site build or a browser preview. Do not adopt it if you want a richer template language, since compatibility with Shopify is the constraint that holds the feature set in place.
- 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 17 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 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Compatibility is the product
LiquidJS describes itself as a Liquid template engine that is Shopify, Jekyll and GitHub Pages compatible, and that word does the heavy lifting. Liquid is a deliberately restricted language: no arbitrary function calls, no expressions with side effects, and a filter pipeline rather than a general expression syntax. Writing your templates in Liquid means a store owner can edit them without a deploy.
Being the JavaScript implementation of that language is the entire value proposition for anyone outside Ruby. A Jekyll site is a Liquid site, and a Shopify theme is a Liquid theme, so the same template text has to produce the same output here.
The engine is written in TypeScript and published as source that compiles to several bundles. The runtime floor is Node.js 16 or newer according to the engines field, and sideEffects is false, so a bundler can drop what you do not import.
The reference to compatibility also implies a ceiling. Extensions that Jekyll or Shopify add, and tags beyond the standard set, exist because the host platform defines them, so the JavaScript side has to track them rather than innovate. The description calls the engine extensible, which is true, but the extensions are additions rather than departures.
One template, three delivery targets
The quick start is two calls:
import { Liquid } from 'liquidjs'
const engine = new Liquid()
const html = await engine.parseAndRender(
'Hello, {{ name | capitalize }}!',
{ name: 'liquid' }
)What you get back is the rendered string, so parseAndRender combines parsing and rendering in one await, which is convenient at asynchronous call sites.
Installation depends on where the code runs. In Node.js it is the ordinary package install:
npm install liquidjsIn a browser you load a UMD bundle from a CDN, which exposes the engine globally rather than as an import:
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>And there is a CLI, which takes the template on the command line and the context as JSON:
npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Liquid"}'That third target is the one to know about if you build static sites, because it means a Liquid template can be rendered without writing a script around it. The setup guide is pointed to for partials, layouts and caching, which are the options that matter once you move past a single string.
package.json shows four bundles and why they exist
The manifest is unusually informative about the distribution strategy. main points at dist/liquid.node.js, module at dist/liquid.node.mjs, and an es2015 field at dist/liquid.browser.mjs. On top of that, a browser field remaps the Node entry points to the browser builds, dist/liquid.browser.umd.js and dist/liquid.browser.mjs, so a bundler that honours the browser field substitutes the right file without any configuration from you.
The build scripts explain the multiplicity. rollup -c rollup.config.mjs is the base, and the same config is reused with a BUNDLES environment variable set to cjs, min or umd, giving build:cjs, build:min and build:umd. The docs pipeline then builds its own minified browser copy and copies it into the documentation theme, so the playground on the site runs the same artifact users install.
Types come from dist/index.d.ts and are produced by typedoc rather than tsc alone, with build:apidoc running typedoc over ./src with the missing exports plugin.
One script deserves attention because it is not a documentation detail: build:charmap runs bin/character-gen.js and redirects its output into src/util/character.ts. A generated source file checked into the tree is what lets the template lexer recognise multi byte characters correctly, and regenerating it is part of the normal build rather than an occasional fix.
The repository also carries eight demo directories, browser, esm, express, nodejs, reactjs, template, typescript and webpack, which is a more reliable guide to supported setups than the prose.
A check script that fails on performance regression
The script worth knowing about is check, which chains build, build:docs, test, lint and perf:diff in that order.
perf:diff runs node bin/perf-diff.js, and there is a separate perf:engines that runs inside the benchmark directory. The naming says what this is: a comparison rather than a single measurement, so a change that makes rendering slower is visible in the project's own workflow rather than only in a user's page load.
Testing is layered. test runs jest, test:coverage adds coverage over src and test/integration, test:e2e targets the end to end suite, and test:demo runs a shell script under test/demo that exercises the demo projects, which is how the browser and webpack builds get checked rather than only the library.
The version script runs the build and the tests before a version bump, so a release cannot skip them, and the lint script covers JavaScript modules and TypeScript sources with eslint configured by .eslintrc.json.
The top level also holds AGENTS.md, CONTRIBUTING.md, SECURITY.md and CHANGELOG.md. The release cadence is fast: v10.30.0 on 2026-10-01, v10.29.0 on 2026-08-11 and v10.28.0 on 2026-08-01, with the last push on 2026-10-05.
Who is running it
The README lists adopters, and the names are informative about where LiquidJS is actually useful. Eleventy and Pattern Lab appear in the README's list of adopters. Directus and Builder.io's Mitosis also appear in the list.
Then there are the platforms themselves: GitHub Docs, Kibana, Shopify CLI and Checkout Blocks, Customer.io, Microsoft Power Pages with the Azure API Management developer portal, and the Syntax podcast site.
The pattern in that list is worth naming. The users are mostly systems where a non engineer writes the template: store owners, documentation editors, content teams. That is the same reason to accept Liquid's restrictions as to choose it in the first place.
For an interactive demonstration, the project publishes a playground where you edit a template and a context and watch the HTML output update live. It is the fastest way to check a filter's behaviour before writing it into a service, and it is the same engine the npm package runs.
Where LiquidJS is the wrong choice
Three boundaries. If your template authors are your own developers and you do not need store owner editable templates, a richer language will be more productive, and the restriction Liquid imposes is the restriction you are paying for. If you need Ruby side behaviour in templates, custom tags have to be reimplemented on the JavaScript side, which is where the compatibility claim starts to cost work.
And if your templates render user supplied content and you treat Liquid as a sandbox, note that the engine's safety comes from Liquid's own design, not from a sandbox around arbitrary code. The security posture of a deployment depends on how templates are stored and who can write them.
The alternative for Node.js is a conventional template language. The reverse choice is staying in Ruby with Liquid itself.
Licensing is MIT, so redistribution must comply with its licence terms.
Editorial conclusion
Adopt LiquidJS when your templates must stay Liquid compatible while running somewhere other than Ruby, whether that is a Node service, a static site build or a browser preview. Do not adopt it if you want a richer template language, since compatibility with Shopify is the constraint that holds the feature set in place. Verify first that your existing templates render as expected, and check the partials, layouts and caching options in the setup guide before your first production render.
Frequently asked questions
How do I install and use LiquidJS?
Run npm install liquidjs, then create a Liquid instance and call parseAndRender with a template string and a context object. The result is a promise resolving to the rendered string.
Can LiquidJS run in a browser?
Yes. The README shows loading dist/liquid.browser.min.js from jsDelivr as a script tag. The package also declares a browser field that remaps the Node entry points to the browser builds for bundlers.
Is LiquidJS compatible with Shopify and Jekyll templates?
The project states Shopify, Jekyll and GitHub Pages compatibility as its defining property. Since those platforms define the dialect, compatibility is also what limits the feature set.
Is there a LiquidJS command line tool?
Yes. Run npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Liquid"}'. It renders a template given inline with a JSON context.
What Node.js version does LiquidJS require?
The engines field in package.json declares node >=16. The current published version is 10.30.0.
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/harttle-liquidjs)