NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #1957 most downloaded on pub.dev
Jinja engine for Hugging Face and GGUF LLM chat templates: messages, tools, generation prompts. Parity-tested against llama.cpp and Jinja2. All platforms, including web and Wasm.
Last release 3 days ago
25 Oct 2026
Release timing varies
gaps range from 4 weeks to 8 months
Nearly every release is documented
notes for 7 of 7 stable releases
Nothing withdrawn
no release was ever pulled
8 months old
7 releases · first in 2026
One column per month.
Add deterministic per-render strftime clocks
Add deterministic per-render strftime clocks
Preserve rendered provenance in the fixed clock callable
strftimeNow(DateTime) for deterministic per-render template clocks, preserving the built-in function's validation and input escaping.Fixed \xHH hexadecimal escapes in template strings, including Flint's chat template.
\xHH hexadecimal escapes in template strings, including Flint's chat template.Fixed a {% filter %} block, and a filter on the output of a block set, a macro or caller(), getting JinjaString.user text already escaped: with x = Ji
{% filter %} block, and a filter on the output of a block set, a macro or caller(), getting JinjaString.user text already escaped: with x = JinjaString.user('<b>'), {% filter upper %}{{ x }}{% endfilter %} is now <B>, not <B>, and {% filter length %}{{ x }}{% endfilter %} is 3, not 9, as in llama.cpp. Input text is now escaped once, when the template outputs it. | safe on such output, {% filter safe %}, and | safe on text made from it or from other safe text escape the input in it, as Jinja2 with autoescape does; | safe on a value passed to render, or returned by a function passed to it, still outputs it unescaped. A block whose output also contains | safe input text is still escaped before the filter runs, as ~ does.tojson and join dropping the input marking, so JinjaString.user text in their output was not escaped: {{ {"k": x} | tojson }} is now {"k": "<b>"} and {{ [x, "<i>"] | join }} is <b><i>. Only input text is escaped, after JSON escaping, in values, dict keys and separators; the JSON's own quotes and template text are not. tojson output with input text in it is no longer safe (is escaped).first, last, reverse, list and for on a string, split and rsplit pieces, strip, lstrip and rstrip with characters, truncate, str(), ~, + and format with a list or dict, string, upper, lower, title, capitalize, trim, replace and indent on a list or dict, replace, indent and format on text that is only partly input, input passed as a replace replacement, input dict keys in a for loop, and an input strftime_now format.replace and indent on input text to leave the template text they add unescaped: x | replace('b', '<i>') is now <<i>>, not <<i>>, and x | indent('> ', true) no longer escapes > . Each character keeps the marking of the text it came from; llama.cpp marks all of the result as input when all of the source is.| safe text being escaped when passed to format, or to replace or indent on input text, sliced with a step or split by unique, and escaped twice when it held escaped input: '{}'.format(x ~ (y | safe)) is now <b><i>, not &lt;b&gt;<i>.<function NAME> and was defined: for an undefined y, {{ y.text }} and {{ y[0]['text'] }} now print nothing and are not defined, as in llama.cpp. As in llama.cpp, y.upper, y.length and the other filters that llama.cpp gives undefined are functions returning an empty value (y.length() is 0, y.default(1) is 1) that print nothing, and calling any other member, such as y.get(), throws. Devstral Small 2507 no longer prints <function text> for an assistant turn without content. Also as in llama.cpp, Command R+ tool use renders an object parameter without additionalProperties as Dict[str, Union[]], not Dict[str, Any], and QwQ-32B throws for an assistant tool-call turn without content before the last turn.x[0] or x[0]['text'] for x = none, to throw Cannot access property with non-string: got Integer, as llama.cpp does; it gave undefined. Devstral Small 2507 with an assistant turn whose content is none now throws, as in llama.cpp.Rewrote the README and example around rendering a chat template, and updated the pubspec description and topics.
lstrip_blocks keeping the indentation before a {% or {# on the line after a block tag, as in {% if x %}\n {% set y = 1 %}. It is now removed, as in llama.cpp and Jinja2. Qwen3-Coder, GLM-4.6, GLM-4.7-Flash, MiniMax-M2, Nemotron-3-Nano and Command R7B prompts no longer contain stray spaces.strftime_now supporting only %Y %m %d %H %M %S. It now formats as llama.cpp does, with C strftime in the C locale as glibc implements it: every conversion (%b, %B, %a, %c, %% and the rest), flags and widths. Like llama.cpp, it throws without a string argument and when the result is empty or 100 bytes or longer.continue and break dropping what the loop pass had already rendered: {% for i in [1, 2] %}{{ i }}{% continue %}{% endfor %} is now 12. A for loop's else block now runs when no pass finishes without continue or break, as in llama.cpp and Jinja2. Output rendered in a nested block before the signal is kept, as in Jinja2; llama.cpp drops it. Output captured by a block set, a filter block, a macro or a caller() body that the signal leaves is discarded, as in llama.cpp.for loop, including its else block, being escaped twice: {% for i in [1] %}{{ x }}{% endfor %} with x = JinjaString.user('<b>') is now <b>.caller() rendering None for a body statement without output, such as {% set %} or a comment, and not escaping JinjaString.user values in the body.select, reject, selectattr, rejectattr and unique on none returning none, so none | selectattr(...) | list was [None]. They now return an empty list, as in llama.cpp. This fixes the functionary v3.1 prompt without tools. default now also replaces none, as in llama.cpp.greet(last='Smith', first='John') with empty values and made add(1, b=10) throw. Arguments now bind positionally, then by name, then from defaults, as in llama.cpp and Jinja2, including caller(...) arguments in a {% call %} block. A missing required argument throws Not enough arguments provided, as in llama.cpp; an unknown keyword or a keyword repeating a positional argument throws, as in Jinja2.- and + failing to parse before anything but a number literal, as in {{ -n }}, {{ items[:-n] }} and {{ not -n }}. As in llama.cpp and Jinja2, they bind tighter than filters, tests and **: -n|abs is (-n)|abs.min and max with attribute returning the attribute value instead of the item, as Jinja2 does. attribute can also be the second positional argument.{{ items.0 }}, {{ {10: 'Bob'}.10 }}), empty subscript (a[] is undefined), int * str repetition, string indent width (indent('> ')) and str.format with {} placeholders. Other format fields, such as {0}, {name}, {{ and {:>5}, throw, as in llama.cpp.BlankExpression to package:dinja/ast.dart for the a[] subscript.example/security_example.dart claiming that plain strings passed to render are escaped; only values wrapped in JinjaString.user are." " (eight spaces in double quotes) being deleted from the output.indent adding a newline to input that ends with one: 'foo\n'|indent is now foo\n, as in llama.cpp and Jinja2. An empty string stays empty with first=true, as in llama.cpp.divisibleby, eq and in, to throw when called without one, as llama.cpp and Jinja2 do. a is divisibleby -a is (a is divisibleby) - a in both, so it now throws instead of rendering -2.* argument unpacking, as in f(*items), to throw, as llama.cpp does. It used to pass the list as a single argument.format filter as llama.cpp has it: it calls str.format, so '{}-{}'|format(1, 2) is 1-2. Jinja2's %-style formatting is not supported: '%s'|format(x) returns %s.Fixed tojson to match llama.cpp: json.dumps spacing ({"a": 1, "b": [1, 2]}), non-ASCII kept unless ensure_ascii=true, floats formatted as C++ %g with
tojson to match llama.cpp: json.dumps spacing ({"a": 1, "b": [1, 2]}), non-ASCII kept unless ensure_ascii=true, floats formatted as C++ %g with 6 significant digits (1.0 is 1, 3.14159265 is 3.14159), non-string keys converted to strings, and positional arguments read as (ensure_ascii, indent, separators, sort_keys). A negative or non-integer indent now gives one-line output, and one separator sets only the item separator.{{ [1, '1'] }} now prints [1, '1']. Input-marked items are still escaped.-0.0 and numbers outside the 64-bit integer range passed to render becoming integers on the web; they are now floats, as on the VM. Other whole-number doubles are still integers on the web, where they are indistinguishable from int.' is written in JSON form, as in ["it's"].Added package:dinja/ast.dart for template analysis without importing src/: parseTemplate, which parses a template into a Program; the AST node types;
package:dinja/ast.dart for template analysis without importing src/: parseTemplate, which parses a template into a Program; the AST node types; and LexerException and ParserException. package:dinja/dinja.dart is unchanged.''.strip() == '' is now true, and safe or input-marked strings equal plain ones.print to stdout when a for loop iterates a function; the error now names the function.1 is eq 1 and 'a' is in ['a'], which failed to parse. As in Jinja2, a value after a test without a comma is now its argument: [x is odd 1] means [x is odd(1)].unique to compare items with Python equality: [1, '1'] keeps both, [1, 1.0, true] keeps one. It now ignores string case by default, as Jinja2 does.ParserException at the end of the template reporting position -1; it now reports the end of the template.{{ 1 +, throwing RangeError instead of ParserException.ParserException from Template on templates with \r\n or a trailing newline: its source, pos, line and col now all refer to the normalized text, as with parseTemplate.unique to follow Jinja2's signature, unique(case_sensitive=false, attribute=none): the first positional argument is now case_sensitive, not attribute. unique now also deduplicates the characters of a string and the keys of a dict.~, string repetition (*), replace, capitalize, title, string, indent and the last piece of split (first of rsplit), matching llama.cpp. Input-marked text from these is now escaped on output like {{ value }}. join still drops the marking, as in llama.cpp.{% set %} block or macro output, is joined with + to plain text.Minimal Jinja2 implementation ported from llama.cpp.
nodes.dart.not in operator logic for Maps.builtins.dart.script/generate_cross_test.py).Your coding agent can read these notes before it upgrades. Set up the MCP server →