Choose a template engine by when the template can change
MiniJinja shows why runtime templates need their own release boundary. Decide who edits them, what they can access and how a bad revision is contained.
The decisive question for a template engine is often when its templates must change. If changing a document requires rebuilding the application, templates can travel with the code and its checks. If an authorized editor must change it while the application is running, the template becomes a separately released input. That choice deserves an explicit interface and operating rules.
MiniJinja provides a useful case. This November 2026 retrospective concerns the boundary described by its author in August 2024. It makes no claim that moving templates outside a binary improves performance or makes an application's interface better.
The requirement that changes the design
In his 27 August 2024 account of building MiniJinja, Armin Ronacher says his infrastructure-automation experiment needed dynamically loaded templates. That excluded a system such as Askama, whose type-safe templates generate Rust code. He describes MiniJinja as a small dynamic language for text generation, implemented with a bytecode compiler and stack-based virtual machine.
The distinction matters beyond HTML. Ronacher also discusses templates for YAML and prompts. A team might reasonably want the Rust application to remain stable while an authorized person revises a generated document. Accepting a dynamic template language can serve that requirement. Rust's static checks cannot, by themselves, establish that a future template supplied at runtime will name the correct fields or produce a valid document.
Suppose, as an illustrative design, a service produces order-confirmation emails. If developers alone change the layout during application releases, compiling templates with the application may be entirely appropriate. If operations staff must change an approved paragraph independently, a runtime template can separate that change from the executable. Neither example establishes a need in a real customer project.
A separate release still needs a release process
Moving a template into a database or file store removes the application rebuild from that edit. It does not remove release work. The editor can still introduce a missing variable, a broken include or a statement the business cannot honour. A syntax check catches only part of that risk.
For the proposed email service, publish a template revision only after rendering it against representative example orders. Include an order with missing optional information and an unusually long product name. Check that required facts remain visible. These are suggested acceptance cases, not tests performed for this article or a claim that MiniJinja validates business wording.
Give each published revision an identifier and keep the previous revision available. Record which template produced a consequential document. Otherwise, a customer can receive text the team cannot reconstruct after the next edit. The application binary's version alone no longer identifies the full output-producing system.
The data interface should also have a stable shape. Expose the fields needed to compose the document and decide how missing values behave. A later application change that removes a field can break a template even if the Rust code compiles. Treat changes to that interface as compatibility work between two independently changing artifacts.
Escaping is specific to the output
Escaping protects a particular output context. An HTML document requires different handling from YAML, a URL or a shell command. The engine name and the implementation language are insufficient evidence that the resulting document is safe. The application must choose and verify the behaviour appropriate to its actual output and engine version.
For the illustrative email, distinguish ordinary customer data from template instructions. A customer's name should remain data. It should not become a second template to interpret. Review any facility that marks text as safe or bypasses escaping, because it changes how inserted values are handled.
For generated configuration, successful template rendering does not establish that the result is valid configuration. Validate the generated format and the meaning expected by its consumer before applying it. For prompts, rendering text likewise does not establish that a downstream model will follow the intended instructions. Those checks belong to the consuming system.
Editing permission is execution authority
A runtime template can repeat work and invoke whatever capabilities its host exposes. Keep the available objects, filters and functions narrow. An editor who only needs to arrange order fields should not receive an object that can fetch unrelated customer records or trigger an external action. This is an application-design recommendation, not a report of a MiniJinja vulnerability.
Resource limits deserve their own decision. Bound the input and output sizes, and establish a suitable execution budget for the selected version and hosting arrangement. Rejecting dangerous capabilities does not automatically bound computation or memory. For untrusted authors, assess isolation and resource enforcement explicitly instead of treating an embedded engine as a universal security sandbox.
A trusted employee can still make an expensive mistake. Preview, revision approval and a rollback route therefore remain useful even when templates are never submitted by customers. The business consequence is a smaller incident: one rejected or reverted document revision rather than a continuing stream of incorrect messages.
When rebuilding is the better bargain
The strongest objection to runtime templates is that independent editing may be unnecessary. Compile-time templates can keep changes within the same review and release process as their data interface. That reduces the number of independently changing parts and can expose errors earlier.
Prefer that arrangement when template edits are infrequent, developers own them and a normal application release is acceptable. The argument for runtime loading becomes strong only when the timing or authorship of edits requires it. Even then, a deliberately limited editor may suit the task better than exposing the full language.
When scoping custom software, write down who can change the document, when the change must take effect and how a rejected revision is recovered. That decision determines the template boundary. MiniJinja's design illustrates why dynamic input can be a sound choice in Rust; the application remains responsible for the authority and release process surrounding it.
Paul