# liquid-rust: Shopify's Liquid template language, implemented in Rust

> liquid-rust is a Rust implementation of the Liquid templating language that Shopify created, with three stated goals, conformance with the reference implementation, flexibility to support variants, and performance within what conformance allows. Its most interesting design decision is that the standard library is empty by default, so you opt in to exactly the filters, tags and blocks your application needs.

**cobalt-org/liquid-rust** — Liquid templating for Rust

- Repository: https://github.com/cobalt-org/liquid-rust
- Website: docs.rs/liquid
- Stars: 588 · Forks: 89
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/cobalt-org-liquid-rust

## Three goals, and the one that constrains the others

The README states the project's goals as a numbered list, and the order matters. Conformant: incompatibilities with strict Shopify Liquid are bugs to be fixed, with a link to a shopify-compatibility label on the repository for tracking them. Flexible: Liquid embraces variants for different domains and the project wants to follow in that spirit. Performant: do the best we can within what is conformant.

That third goal is explicitly subordinate to the first two, which is an unusual and useful thing for a project to state plainly. A templating engine that is fast but subtly wrong is worse than one that is slower and correct, because the wrongness only shows up as a rendering difference somewhere downstream. The compatibility label tracker is the mechanism for that, and it is worth a user checking before adopting the crate for a Shopify-flavoured workflow.

The project is hosted under the cobalt-org organisation, whose homepage is docs.rs/liquid, so the published crate is liquid. The primary language is Rust, the licence is MIT at the repository level, the default branch is master, and the last push was on 2026-10-20 with the project not archived.

The release history is where a reader should slow down. The recent releases are v0.16.0 and v0.15.0, both from 2018-10-04, and v0.14.1 from 2018-01-24. So the tagged releases are eight years old while the source is being worked on today, which means the version you get from crates.io is not necessarily the version the repository currently holds.

The licence is the other thing to check. The README states the code is licensed under either the Apache License, Version 2.0, or the MIT licence, at your option, and the Cargo.toml workspace package records the same as MIT OR Apache-2.0. The contribution terms follow the same dual licensing, with any contribution dual licensed as defined in the Apache-2.0 licence.

## Getting the crate in and rendering a template

The usage section is short. To include liquid in your project, add the following to your Cargo.toml, and the modern way to do that is the cargo add command:

```console
$ cargo add liquid
```

The example that follows is worth reading in full, because it shows the shape of the API:

```rust
let template = liquid::ParserBuilder::with_stdlib()
    .build().unwrap()
    .parse("Liquid! {{num | minus: 2}}").unwrap();

let globals = liquid::object!({
    "num": 4f64
});

let output = template.render(&globals).unwrap();
assert_eq!(output, "Liquid! 2".to_string());
```

Three things stand out. The parser is built through a ParserBuilder where build() returns a Result that has to be unwrapped, so a malformed standard library configuration is a startup error rather than a runtime surprise. The template source is the string Liquid! {{num | minus: 2}}, which shows the filter syntax with a pipe and a colon, matching the reference language. And the globals are built with the liquid::object! macro, which produces a map-like value that the render call takes by reference.

The render method returns a Result too, and the assertion in the example checks the output against the expected string. That is worth noting as a design point: rendering can fail, and the API says so rather than panicking or returning an empty string.

The documentation also points at a reference on Liquid syntax in the Shopify wiki, described as a resource for Liquid for Designers, which is the natural companion document for anyone writing the templates themselves.

## The empty standard library, and why that is the right default

The customisation section opens with a decision that will surprise anyone coming from a template engine with a big built-in filter set. By default, liquid-rust has no filters, tags, or blocks. You can enable the default set or pick and choose which to add to suit your application.

This is a good default for a library that a server application links into, for two reasons. The obvious one is surface area, since every filter and tag in the standard library is code you have chosen to carry, and a server that renders user-supplied templates is better off with a small set of vetted operations than with the full catalogue. The subtler reason is that a filter is a capability, and capabilities in a template language are a security surface. A template that can reach into arbitrary parts of the runtime is a template that can do more than render text, and an empty default means the decision to grant that capability is explicit.

The parser builder is the single place where all of this is configured, and the same builder is the extension point for everything below. The three extension mechanisms are filters, tags and blocks, and each has a distinct shape.

Filters are simply functions or closures that take an input Value and a Vec<Value> of optional arguments and return a Value to be rendered or consumed by chained filters. They register by calling liquid::ParserBuilder::filter, and the README points at the filters directory for what an implementation looks like.

Tags are made up of two parts, the initialization and the rendering. Initialization happens when the parser hits a Liquid tag that has your designated name, and you specify a function or closure that returns a Renderable object to do the rendering. Tags register by calling liquid::ParserBuilder::tag, with include_tag.rs as the reference implementation.

Blocks work very similar to Tags. The only difference is that blocks contain other markup, which is why block initialization functions take another argument, a list of Elements that are inside the specified block. Blocks register by calling liquid::ParserBuilder::block, and comment_block.rs is the example to read.

The progression from filter to tag to block maps onto how much control the template author has, and the fact that the library gives you all three at the same level of effort is what makes it usable as a template host rather than only as a configuration formatter.

## Who is actually using it, and what the workspace looks like

The README names four example applications using liquid-rust, and they are a good signal of the intended use cases.

Cobalt is a static site generator. Cargo-tarball is a crate binary packaging tool. Cargo-generate is a crate generator from templates. And Mandy is described as a hypersonic, easy-to-use, performant static-site generator.

Three of the four are static site generators or template-driven code generators, which tells you the sweet spot. Liquid is good at the kind of work where a template has access to a fixed set of variables and the output is a document, a crate skeleton or a site. It is not obviously the right tool for a request-time template language in a multi-tenant service, although nothing prevents that.

The repository layout is that of a Rust workspace. The Cargo.toml declares a workspace with the members glob crates/*, and the workspace package sets the repository, the MIT OR Apache-2.0 licence, edition 2021 and an MSRV of 1.83.0. There are both a src/ and a crates/ directory, which is the usual arrangement for a workspace where the crates subdirectory holds the individual packages and src holds the root or facade crate.

The engineering discipline in the tree is more visible than usual for a small library. There is a .clippy.toml, a pre-commit configuration, a deny.toml for dependency policy checks, a release.toml for the release process, a typos.toml for spell checking, and a committed.toml. The workspace lints section is extensive, enabling rust_2018_idioms plus warnings for unnameable_types, unreachable_pub, unsafe_op_in_unsafe_fn, unused_lifetimes, unused_macro_rules and unused_qualifications, and then a long list of clippy rules at warn including float_cmp_const, lossy_float_literal, from_iter_instead_of_collect, macro_use_imports and mutex_integer.

The float comparison rules are worth noting. A templating engine that renders numbers has to decide how to format them, and the strictness here is a signal that the rendering path is treated as something worth being careful about rather than a string concatenation helper. The presence of benches/ and a deny.toml for supply chain checks on dependencies suggests the same level of care.

## How to evaluate it, and the version question to settle first

The evaluation turns on two questions, and the second one is easy to miss.

The first is whether conformance to Shopify Liquid matters to you. If you are migrating templates from a Shopify storefront or a Rails application using the reference implementation, then the shopify-compatibility label tracker is the thing to read before you commit, because every open issue there is a behaviour difference you would inherit. If you are writing templates from scratch, the empty default standard library means you are not paying for behaviour you will never use, and the flexibility goal is what you are buying.

The second question is which version you get. The tagged releases end in 2018, and the source tree has moved a long way since, with an MSRV of 1.83.0 and a modern lint configuration that would not have existed in 2018. So the choice is between the published crate, which is a known point in time, and building from the repository, which is current but unreleased. A project that depends on the published crate is pinning a version whose behaviour may differ from what the README describes, because the README describes the repository, not the last tag.

That distinction is not stated in the README, so it is worth checking yourself. Read the CHANGELOG.md to see what has changed since v0.16.0, and if you need the newer behaviour, decide whether tracking a git dependency is something you are willing to own.

For what it is worth, the library's shape is well suited to embedding. The ParserBuilder is a single configuration point, the three extension mechanisms cover the range from a value transformation to a block of markup, and the Result-based rendering API means failures are values you can handle. If your template needs to do something the language has no filter for, you write a filter, and if it needs a new tag you write a tag, and neither requires forking the parser.

The one thing to keep in mind is that a template language in a server process is a capability boundary, and this library gives you the tools to grant capabilities deliberately. That is a feature, but it means the review of which filters and tags you enable is part of deploying it safely.

## Conclusion

Adopt liquid-rust if you are writing a Rust application that needs a real template language with the ability to add your own filters, tags and blocks, and if you want a small standard library you control rather than a large one you inherit. Do not adopt it expecting the current source behaviour from the published crate, because the recent tagged releases date from 2018 while the repository has moved on, so read the CHANGELOG and decide whether you take the published version or track the repository. Before you commit, check the shopify-compatibility label if you are migrating templates from a reference implementation, and review which filters and tags you enable, because in a server process a template filter is a capability and the empty default makes that review a decision rather than an oversight.

## FAQ

### What is liquid-rust?

It is a Rust implementation of the Liquid templating language created by Shopify, published on crates.io as liquid. The primary language is Rust, the homepage is docs.rs/liquid, and the last push to the repository was on 2026-10-20.

### What are the goals of the liquid-rust project?

Three: conformant, so incompatibilities with strict Shopify Liquid are bugs to be fixed and tracked on a shopify-compatibility label; flexible, following the spirit of Liquid's variants for different domains; and performant, doing the best it can within what is conformant.

### How do I add liquid-rust to a Rust project and render a template?

Run cargo add liquid, then build a parser with liquid::ParserBuilder::with_stdlib(), parse your template string, create globals with the liquid::object! macro, and call template.render(&globals), which returns a Result.

### Does liquid-rust include any filters or tags by default?

No. By default liquid-rust has no filters, tags, or blocks, and you enable the default set or pick and choose which to add. Filters, tags and blocks are all registered through the ParserBuilder, with liquid::ParserBuilder::filter, .tag and .block respectively.

### What licence does liquid-rust use?

The code is licensed under either the Apache License, Version 2.0, or the MIT licence, at your option, and the Cargo.toml records it as MIT OR Apache-2.0. Contributions are dual licensed on the same terms unless the contributor states otherwise.

## Sources

- [cobalt-org/liquid-rust on GitHub](https://github.com/cobalt-org/liquid-rust)
- [Issues](https://github.com/cobalt-org/liquid-rust/issues)
- [License: MIT](https://github.com/cobalt-org/liquid-rust/blob/master/LICENSE)
- [README](https://github.com/cobalt-org/liquid-rust/blob/master/README.md)
- [Releases](https://github.com/cobalt-org/liquid-rust/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cobalt-org-liquid-rust
