Return Safe Value Strings from Filter in Rust's MiniJinja

December 2025

I've got some filters that do syntax highlighting in my personal static site builderssb. They look like this:

[! filter highlight_html|safe !]
<div>the quick brown fox</div>
[! endfilter !]

Without applying |safe to the filter each time the output gets double escaped (i.e. the tags that do the highlighting are visible in the output instead of doing the actual highlighting).

The reason is that my original filters were returning String content. By using a Value instead that's created with Value::from_safe_string() the need to add |safe goes away.

Here's an example of how things work:

---
[dependencies]
anyhow = "1.0.100"
minijinja = { version = "2.13.0", features = ["custom_syntax", "loader"] }
---

#![allow(warnings)]
use anyhow::Result;
use minijinja::{Environment, Value, context};
use minijinja::syntax::SyntaxConfig;

fn main() -> Result<()> {
    let mut env = get_env();
    env.add_template_owned(
        "template", template_content()
    )?;
    let jinja = env.get_template("template")?;
    let test_string = "&lt; div &gt; ".to_string();
    let output = jinja.render(context!(test_string => test_string))?;
    println!("{}", output);
    Ok(())
}

fn get_env() -> Environment<'static> {
    let mut env = Environment::new();
    env.add_filter("filter_with_safe", filter_with_safe);
    env.add_filter("filter_without_safe", filter_without_safe);
    env.set_syntax(
        SyntaxConfig::builder()
            .block_delimiters("[!", "!]")
            .variable_delimiters("[@", "@]")
            .comment_delimiters("[#", "#]")
            .build()
            .unwrap(),
    );
    env
}


fn filter_with_safe(input: String) -> Value {
  Value::from_safe_string(input)
}

fn filter_without_safe(input: String) -> String {
  input
}

fn template_content() -> String {
r#"
[!- autoescape true !]
The example input is already escaped. 

Running this filter without safe results in 
douple escaping:

[! filter filter_without_safe !]
[@- test_string @]
[!- endfilter !]

The same filter can be run with `|safe`
so that only single escaping occurs

[! filter filter_without_safe|safe !]
[@- test_string @]
[!- endfilter !]

Or, the filter that uses `Value::from_safe_string()`
can be used without resuqing `|safe` 
to be applied each time the filter is used. 

[! filter filter_with_safe !]
[@- test_string @]
[!- endfilter !]
[!- endautoescape !]
"#.to_string()
}
Output:
The example input is already escaped. 

Running this filter without safe results in 
douple escaping:

&amp;amp;lt; div &amp;amp;gt; 

The same filter can be run with `|safe`
so that only single escaping occurs

&amp;lt; div &amp;gt; 

Or, the filter that uses `Value::from_safe_string()`
can be used without resuqing `|safe` 
to be applied each time the filter is used. 

&amp;lt; div &amp;gt;

It's a nice little quality of life improvement MiniJinja continues to impress.

- a

end of line

Endnotes

This is for my personal static site builder.

It's super customized to my workflow. You can check out the source code if you're curious about how it works.

References

The bad ass rust templating engine.