# handlebars-rust: the Handlebars template language implemented for Rust

> handlebars-rust is a Rust implementation of the Handlebars templating language, offering template inheritance, a helper system written in Rust or in Rhai scripts, auto-reload in dev mode, and WebAssembly compatibility, with a deliberate policy of keeping only if and each as built-in control directives.

**sunng87/handlebars-rust** — Rust templating with Handlebars

- Repository: https://github.com/sunng87/handlebars-rust
- Stars: 1,488 · Forks: 158
- Language: Rust
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/sunng87-handlebars-rust

## Two APIs, and a deliberate refusal to fuse them

handlebars-rust is the Handlebars templating language implemented in Rust and for Rust. The quick start shows both ways to use it:

```rust
use handlebars::Handlebars;
use serde_json::json;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let mut reg = Handlebars::new();
    // render without register
    println!(
        "{}",
        reg.render_template("Hello {{name}}", &json!({"name": "foo"}))?
    );

    // register template using given name
    reg.register_template_string("tpl_1", "Good afternoon, {{name}}")?;
    println!("{}", reg.render("tpl_1", &json!({"name": "foo"}))?);

    Ok(())
}
```

The registry object is `Handlebars`, and there are two ways to render. `render_template` takes a template string and renders it immediately, with no registration, and `render` takes a name that was previously registered with `register_template_string`. The data is a `serde_json` value, so any JSON-serialisable structure works, and the return type is a `Result` because rendering can fail.

The first design decision is stated as a principle rather than a feature. The library doesn't attempt to use some macro magic to allow you to write your template within your Rust code, and the author's comment is that it is fun to do that but it doesn't fit real-world use cases.

That refusal is the interesting part of the design, because writing templates inline in Rust is what many Rust templating crates do, and it is genuinely a mistake for a real application. A template embedded in a string literal is not a file, so it cannot be edited by a designer, cannot be translated, cannot be cached separately, and cannot be previewed in a browser without a running server. The isolation of Rust and HTML is the separation of the two, and the fact that the author calls the alternative fun before dismissing it is worth noting, because it is a deliberate trade rather than an oversight.

The library is categorised as template-engine and web-programming, the licence is MIT, the edition is 2024 with a rust-version of 1.85, and the crate is published on crates.io as `handlebars` with the docs on docs.rs.

## Only if and each, on purpose

Only essential control directives `if` and `each` are built-in. The stated reason is that this prevents you from putting too much application logic into your template.

That is a design position rather than a missing feature, and it is the one that most affects how you write templates for this library. A template engine with a richer directive set lets the template compute, iterate with a complex expression, compare with compound conditions, and reach into helper functions to do real work, and each addition is a step towards the template becoming a second application layer.

With only if and each, the template can branch and it can loop, and anything more is a helper you write in Rust, where it can be unit tested, typed, and reviewed as code. The point of the restriction is that the logic ends up in the language with a compiler rather than in a string with a parser.

There is a wrinkle worth knowing, which comes from the helper side rather than the directive side. By default, handlebars-rust ships additional helpers, compared with the original JavaScript version, that are useful when working with `if`. So the practical template vocabulary is the two directives plus a set of extra comparison helpers, and that is what the design intends.

The other thing the library does not do is let a template reach arbitrary Rust. There is no escape hatch for arbitrary computation, because a template engine that can call anything is a template engine that will eventually call something dangerous. The extension mechanism is the helper, and the helper is code you wrote.

The example set in the source tree maps onto these ideas directly. The `quick` example is the very basic one of the registry and render APIs. The `render` example covers how to define custom helpers with a function, a trait implementation, or a macro, and how to use them. The `helper_macro` example demonstrates the `handlebars_helper!` macro to simplify helper development. The `decorator` example shows how to use a decorator to change data or define a custom helper. The `error` example is a simple case for error handling. The `partials` example demonstrates template inheritance with Handlebars, and it comes with its own template files. The `render_file` example is like render but renders to a file instead of a string. The `script` example shows how to define a custom helper with the Rhai scripting language, just like using JavaScript for handlebarsjs. The `dev_mode` example hosts handlebars in dev mode, so you can edit the template and see the change without restarting your server.

## Helpers in Rust, or helpers in Rhai

The helper system is where the extension happens, and the project makes the point simply: you can write your own helper with Rust, it can be a block helper or an inline helper, and you put your logic into the helper and don't repeat yourself.

A helper can be as simple as a Rust function:

```rust
handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));

/// register the helper
handlebars.register_helper("hex", Box::new(hex));
```

And using it in your template:

```handlebars
{{hex 16}}
```

The `handlebars_helper!` macro turns a plain closure into something registerable, and `register_helper` takes a boxed function. The rendering shows a template calling a Rust function with an argument, which is the whole point: the logic is in Rust, where the type of the argument is declared and the compiler checks the call.

The second route is scripting. With the `script_helper` feature flag enabled, you can also create helpers using rhai script, just like JavaScript for handlebars-js. The project is careful about the maturity of this feature, noting that it was in an early stage, that its API was limited at the moment, and that it can change in the future.

The summary puts the ambition plainly: fully scriptable, with rhai script support you can implement your own helper with the scripting language, and together with the template language itself, template development can be fully scriptable without changing Rust code.

That is a meaningful capability for a specific workflow. A team where the person writing templates is not the person writing Rust can add a helper in a scripting language and ship it without a rebuild of the Rust service. The cost is that the logic is then dynamically typed and interpreted rather than compiled, which is the same trade every scripting extension makes.

The `script_helper` feature is optional and not in the default feature set, so the Rhai dependency is not pulled in unless you ask for it.

## Template inheritance, and why include is not enough

Template inheritance is the feature the author is most emphatic about, and the reasoning is about reuse rather than about features.

Every time the author looks into a templating system, they will investigate its support for template inheritance, and the argument is that template include is not sufficient for template reuse. In most cases you will need a skeleton of page as parent, with a header and a footer and so on, and embed your page into that parent.

That is a distinction worth holding on to, because include and inheritance solve different problems. Include substitutes a fragment at a point. Inheritance defines a page skeleton with named slots that a child template fills in. A layout with a header, a sidebar, a content area, and a footer is inheritance. A shared header snippet is include. Most real page designs need the first, and an engine that only offers the second gives you a pile of includes and no structure.

The real example is in `examples/partials.rs` and the templates used by that file, and the examples directory shows that the templates for each example live in a directory next to the Rust file, with `examples/partials/`, `examples/render/`, `examples/render_file/`, `examples/decorator/`, `examples/error/`, `examples/helper_macro/`, `examples/script/`, and `examples/dev_mode/`.

That layout also connects to the dev mode feature, which is the other piece that makes templates workable. By turning on `dev_mode`, handlebars auto reloads any template and scripts that loaded from files or directory, and the project says this can be handy for template development.

Auto-reload only works for templates loaded from a file or a directory, which is exactly why the `dir_source` feature exists, with `walkdir` as its dependency. A template registered as a string cannot be reloaded, because there is nothing to reload it from.

The `rust-embed` dependency, optional, is the other side of this, and it lets templates be compiled into the binary, which is the arrangement you want in production where there is no directory of template files to ship.

## WebAssembly, a web playground, and the version policy

Two of the project's claims are about reach rather than about templates.

Handlebars 3.0 can be used in WebAssembly projects, and the repository carries the evidence for it. There is a `wasm/` directory, a `build-wasm.sh` script, and a `playground/` directory. There is a GitHub action to compile the latest master branch into WebAssembly and serve it on GitHub Pages, and the project says you can test and verify your template with both handlebars-rust and handlebarjs, which is a compatibility claim you can check yourself rather than take on trust.

That playground is the most useful thing for evaluating the library, because it puts the two implementations side by side. If you are considering this because you have JavaScript templates and want to keep them, the question is whether the Rust implementation behaves the same, and a page that runs both is the fastest answer.

The version policy is stated clearly and is worth knowing before you pin. Handlebars will track Rust nightly and stable channel, and when dropping support for previous stable versions, the maintainer will bump the patch version and clarify it in the changelog.

The unusual part is that a patch version bump can remove a Rust version. That is a consequence of tracking the stable channel closely, and it means the crate's version number does not tell you the minimum Rust version. The rust-version field in the manifest does, and it is currently 1.85 with edition 2024.

The rest of the tree shows the working practices. There is a `benches/` directory with criterion, the benchmarks, and a `profile.sh` for running them. The `tests/` directory holds the test suite, and there is a `rustfmt.toml` for formatting. The `release.toml` is the release-plz configuration for automating releases, and the `CHANGELOG.md` is in the source tree.

The release history is a steady patch stream: v6.4.0 in January 2026, v6.4.3 in July 2026, and v6.4.4 in August 2026, with the last push on 2026-10-18. The maintainer asks for help, listing several Help Wanted issues with the difficulty level labelled, and says they are always looking for maintainers to work together on the library.

## Where handlebars-rust is the wrong tool

The limits are mostly about the template model, and they are the ones the project chose rather than ones it suffered.

First, if your templates need logic, this library is going to fight you. The built-in directive set is if and each, and anything else is a helper. That is a sound default, and it is also a constraint: a template that in another engine would compute a filtered and sorted list in a dozen lines becomes a helper call plus a loop. Teams whose templates have grown logic will find the migration work larger than the benefit.

Second, the crate follows Rust's release cadence closely enough that a patch version can raise the minimum Rust version. In an application whose dependency tree is pinned and whose CI image is fixed, that is a periodic upgrade chore, and the project is upfront that it is deliberate.

Third, the WebAssembly support comes with the usual shape of a library that runs in a browser. The playground compiles master to WASM, which is the right way to publish a demo, and it is not the same thing as the library being tested in a browser, where the memory behaviour and the performance of the template parser are your problem.

Fourth, the scripting extension is the feature to be careful with. The project says it was in an early stage, that its API was limited, and that it can change in the future. So the Rhai route is the right choice when your template authors are not Rust developers, and the wrong choice when the helper is performance-critical or needs a type guarantee.

Fifth, the compatibility question is real and only partly answered. The project says the playground lets you verify a template with both handlebars-rust and handlebarjs, which is a strong claim about intent, and it also says the shipped helpers differ from the original JavaScript version, with additional helpers that are useful when working with `if`. So a template written for Handlebars in JavaScript will not always behave identically, and the differences are not all in the direction of extra features.

Sixth, and this is the practical one for an existing application, this library does not do the thing many Rust templating crates do, which is let you write a template inside your Rust code. If your team wants that, this is the wrong crate and you should pick one that offers it, because the author is not going to add it.

## Conclusion

handlebars-rust fits a Rust service that wants to share templates with a JavaScript front end, or a team that wants Handlebars' inheritance and helper model without running a JavaScript engine to do it. It is the wrong choice if you want logic-heavy templates, because the built-in control set is deliberately limited to if and each. Check three things before you adopt it: that you read the isolation of Rust and HTML section, because the library deliberately refuses to let you write templates inline in Rust code, that the minimum Rust version policy is acceptable to you, since the crate tracks nightly and stable and drops old stable versions in a patch release, and that you check the feature flags, because the scripting, directory source, and embedded template support are all opt-in.

## FAQ

### What is handlebars-rust and how do I render a template with it?

It is the Handlebars templating language implemented in Rust and for Rust. You create a `Handlebars` registry and either call `render_template` with a template string, or call `register_template_string` and then `render` by name, passing your data as a `serde_json` value.

### Which control directives does handlebars-rust support?

Only `if` and `each` are built in, deliberately, to prevent too much application logic ending up in a template. The library also ships additional helpers compared with the original JavaScript version, which are useful when working with `if`, and anything beyond that is a helper you write in Rust or in a Rhai script.

### How do I write custom helpers in handlebars-rust?

In Rust, with the `handlebars_helper!` macro turning a closure into a registerable helper, for example `handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));` and then `handlebars.register_helper("hex", Box::new(hex));` to use it as `{{hex 16}}`. With the optional `script_helper` feature you can also write helpers in the Rhai scripting language, like JavaScript in handlebars-js.

### What is the minimum Rust version policy for handlebars-rust?

Handlebars will track the Rust nightly and stable channel, and when support for previous stable versions is dropped, the maintainer bumps the patch version and clarifies it in the changelog. The current manifest declares edition 2024 with a rust-version of 1.85, so the crate version alone does not tell you the minimum Rust version.

## Sources

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

---

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