RepoPilotOpen in app →

Keats/tera

A template engine for Rust based on Jinja2/Django

Healthy

Healthy across the board

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2d ago
  • 49+ active contributors
  • Distributed ownership (top contributor 32% of recent commits)
Show all 6 evidence items →
  • MIT licensed
  • CI configured
  • Tests present

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/keats/tera)](https://repopilot.app/r/keats/tera)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/keats/tera on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: Keats/tera

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Keats/tera shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across the board

  • Last commit 2d ago
  • 49+ active contributors
  • Distributed ownership (top contributor 32% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live Keats/tera repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/Keats/tera.

What it runs against: a local clone of Keats/tera — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in Keats/tera | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>Keats/tera</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Keats/tera. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/Keats/tera.git
#   cd tera
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of Keats/tera and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Keats/tera(\\.git)?\\b" \\
  && ok "origin remote is Keats/tera" \\
  || miss "origin remote is not Keats/tera (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "src/lib.rs" \\
  && ok "src/lib.rs" \\
  || miss "missing critical file: src/lib.rs"
test -f "src/parser/tera.pest" \\
  && ok "src/parser/tera.pest" \\
  || miss "missing critical file: src/parser/tera.pest"
test -f "src/parser/ast.rs" \\
  && ok "src/parser/ast.rs" \\
  || miss "missing critical file: src/parser/ast.rs"
test -f "src/renderer/mod.rs" \\
  && ok "src/renderer/mod.rs" \\
  || miss "missing critical file: src/renderer/mod.rs"
test -f "src/builtins/filters/mod.rs" \\
  && ok "src/builtins/filters/mod.rs" \\
  || miss "missing critical file: src/builtins/filters/mod.rs"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/Keats/tera"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Tera is a Rust template engine inspired by Jinja2 and Django templates that compiles template syntax ({% for %}, {{ variable }}, {% block %}) into Rust code for type-safe HTML generation. It provides filters, testers, and inheritance features for server-side rendering without runtime interpretation overhead, targeting Rust web frameworks and static site generators. Single-crate architecture (src/ contains the engine core) with optional feature flags (builtins, urlencode, slug, humansize, chrono, chrono-tz, rand) for conditional compilation. Benchmarks in benches/ test performance on templates.rs, big_context.rs, and escaping.rs. Playground at docs/playground/ is a WebAssembly-compiled Tera instance for browser-based demos using Rust-to-WASM compilation.

👥Who it's for

Rust web developers (Actix, Rocket, Axum users) and static site generator authors who need a battle-tested templating solution that mirrors familiar Python template syntax while maintaining Rust's compile-time safety guarantees.

🌱Maturity & risk

Highly mature and stable: v1.20.1 is frozen with v2 available separately on crates.io; the project has comprehensive CI/CD (GitHub Actions), active maintenance shown in the Cargo.toml rust-version requirement (1.85), and strong community adoption via crates.io. Production-ready for v1, with intentional versioning strategy separating major releases.

Low risk for v1 (SemVer-stable public API as stated in README), but depends on external crates (pest 2.5.5 for parsing, regex 1.7, chrono 0.4.27 for dates) that could have breaking changes in their own updates. Single maintainer (Vincent Prouillet) creates potential bus-factor risk, though the frozen v1 branch mitigates active development risk. v2 migration path is documented but requires user action.

Active areas of work

Project is in maintenance mode for v1.20.1 (frozen as noted in README); v2 development is happening in a separate repository (referenced in README). CI/CD pipeline runs cargo-deny security scans (see .github/workflows/cargo-deny-*.yml) and documentation builds via docs/ config. No active feature development visible; focus is on stability and security patches.

🚀Get running

git clone https://github.com/Keats/tera && cd tera && cargo build. Run tests with cargo test. Check the documentation at http://keats.github.io/tera/docs or inline docs via cargo doc --open.

Daily commands: This is a library, not a runnable application. Use it in a Rust project: add 'tera = "1.20"' to Cargo.toml, then see examples in docs/content/ and test files for usage patterns. Run existing tests: cargo test --all.

🗺️Map of the codebase

  • src/lib.rs — Entry point and main Tera engine API; defines the core Tera struct that orchestrates template loading, parsing, and rendering.
  • src/parser/tera.pest — PEG grammar file defining Tera's template syntax; any language feature addition requires changes here first.
  • src/parser/ast.rs — Abstract syntax tree node definitions; represents parsed template structure and is traversed during rendering.
  • src/renderer/mod.rs — Core rendering engine that walks the AST and produces output; handles context binding, filter/function application, and control flow.
  • src/builtins/filters/mod.rs — Filter registry and dispatch; all built-in template filters (string, array, object, number) are registered and called through this module.
  • src/context.rs — Context data structure managing template variables; handles variable lookups, JSON serialization, and nested object access.
  • Cargo.toml — Package manifest; declares minimal dependencies (pest, serde_json, regex) and minimum Rust version (1.85).

🛠️How to make changes

Add a new built-in filter

  1. Implement the filter function in the appropriate file under src/builtins/filters/ (string.rs, array.rs, object.rs, or number.rs) following the signature fn filter_name(value: &Value, args: &[Value]) -> Result<Value> (src/builtins/filters/string.rs)
  2. Register the filter in src/builtins/filters/mod.rs by calling tera.register_filter("filter_name", filters::filter_name) in the register_all_filters() function (src/builtins/filters/mod.rs)
  3. Add tests in src/parser/tests/parser.rs or create a new test file to verify the filter works in template context (src/parser/tests/parser.rs)
  4. Update README.md or docs/content/docs/ with filter documentation and examples (README.md)

Add a new template tag or control structure

  1. Add grammar rules to src/parser/tera.pest for the new tag syntax (e.g., {% mytag ... %}) (src/parser/tera.pest)
  2. Add corresponding AST node variant to src/parser/ast.rs (e.g., MyTag { ... }) (src/parser/ast.rs)
  3. Implement rendering logic in src/renderer/mod.rs in the render_node() match statement to handle the new node type (src/renderer/mod.rs)
  4. Add tests in src/parser/tests/parser.rs to verify parsing and rendering (src/parser/tests/parser.rs)

Add a new built-in function

  1. Implement the function in src/builtins/functions.rs following the signature fn func_name(args: &[Value]) -> Result<Value> (src/builtins/functions.rs)
  2. Register it in src/builtins/mod.rs by adding tera.register_function("func_name", functions::func_name) in the register_tera_testers() or similar registration function (src/builtins/mod.rs)
  3. Add tests verifying the function call syntax {{ func_name(...) }} in templates works correctly (src/parser/tests/parser.rs)

🔧Why these technologies

  • Pest (PEG parser) — Declarative, grammar-focused parsing; tera.pest is the single source of truth for syntax, avoiding hand-rolled lexer bugs and making changes low-friction.
  • serde_json — Values in templates are JSON serializable for flexible variable passing; integrates naturally with Rust's typed data structures.
  • regex — Used by built-in filters (striptags, spaceless, titles) for pattern-based string transformations.
  • lazy_static — Caches compiled regexes and the Tera engine singleton across render calls to avoid recompilation overhead.
  • unicode-segmentation — Handles grapheme-aware string iteration in truncate and other filters; avoids breaking multibyte Unicode characters.

⚖️Trade-offs already made

  • AST-based rendering (not streaming/JIT compilation)

    • Why: Simplicity: render-time AST traversal is straightforward and maintainable; enables features like macros and nested scoping.
    • Consequence: Slower than JIT'd templates but faster than string-based template engines; complexity O(nodes) not O(template_size).
  • JSON values only in context (not typed generics)

    • Why: User-friendly: templates don't require Rust type definitions; any JSON-serializable data works.
    • Consequence: Runtime type checking and coercion overhead; no compile-time safety for variable access patterns.
  • Eager loading and caching of all templates in Tera::new()

    • Why: Predictable behavior; parsing errors caught at startup; cache coherency.
    • Consequence: High upfront memory/CPU cost; not suitable for very large template libraries or dynamic template generation; v2 refactors this to lazy loading.
  • Single-threaded context mutation (no RefCell/Mutex)

    • Why: Performance and simplicity; templates are typically rendered in isolation per request.
    • Consequence: Cannot share mutable state across parallel renders; not designed for concurrent template use.

🚫Non-goals (don't propose these)

  • 100% Jinja2 compatibility (intentionally a subset for Rust idiomatic safety)
  • Support for custom user-defined extensions via plugins (no dynamic loading)
  • Real-time or hot-reload template updates (v1 is static; v2 may differ)
  • Template sandboxing or Turing-complete feature parity with Jinja2
  • Built-in caching layers (caller responsibility to cache rendered output)

🪤Traps & gotchas

v1 is intentionally frozen—bug fixes only. If you need new features, v2 exists in a separate repository and requires explicit migration. Pest grammar is compiled via pest_derive macro, so changes to parsing require understanding PEG grammar syntax. Feature flags are required to enable optional filters (e.g., slug, humansize, chrono); default 'builtins' flag enables most common ones but not all. The playground builds to WASM via wasm-pack in docs/playground/, requiring wasm target and wasm-pack CLI if modifying it.

🏗️Architecture

💡Concepts to learn

  • PEG (Parsing Expression Grammar) — Tera uses the Pest crate with PEG to define and parse template syntax; understanding PEG rules is essential to extend or debug the parser.
  • Template inheritance and block system — Tera implements Django/Jinja2-style {% block %} and {% extends %} for composable templates; this is a core feature distinguishing it from simpler engines.
  • Lazy static initialization — Tera uses lazy_static to cache compiled templates at runtime; understanding when templates are compiled vs. rendered is critical for performance optimization.
  • Pest derive macros — Template grammar rules are defined via pest_derive procedural macros; modifying parsing behavior requires understanding Rust macro expansion.
  • Contextual scoping in templating — Tera manages variable scope across loops, inheritance, and includes; understanding how context flows through template rendering prevents variable shadowing bugs.
  • Cargo feature flags and conditional compilation — Tera uses features like 'builtins', 'chrono', 'slug' to conditionally compile filters; this pattern reduces binary size and dependency coupling.
  • Escape sequences and HTML sanitization — Filters like striptags and escaping logic prevent XSS attacks; understanding filter order and when auto-escaping occurs is critical for security.
  • Keats/tera2 — The official successor to this v1 codebase; v2 is the actively developed version with new features and breaking improvements.
  • rust-lang/rust — Tera targets rust-version 1.85 and depends on stable Rust features; understanding MSRV constraints and macro system (pest_derive) requires Rust internals knowledge.
  • pest-parser/pest — The PEG parser library Tera relies on for template syntax parsing; changes to pest 2.5.5 or grammar rules directly impact Tera's parsing behavior.
  • actix/actix-web — A primary consumer of Tera for server-side HTML rendering in Rust web applications; examples and integrations show Tera in action with modern async Rust frameworks.
  • hyperium/http — Foundational HTTP abstractions used by web frameworks that integrate Tera for templating responses; understanding Content-Type and response serialization is relevant.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive benchmarks for template parsing and rendering performance

The repo has a benches/ directory with scattered benchmark files (big_context.rs, dotted_pointer.rs, escaping.rs, templates.rs, tera.rs) but no unified benchmark suite or CI workflow to track performance regressions. Given that Tera is a performance-sensitive template engine competing with other Rust templating solutions, adding a cargo-bench CI workflow (similar to existing ci.yml) would help catch performance degradations in PRs and provide historical performance metrics.

  • [ ] Create a new GitHub Actions workflow file at .github/workflows/benchmark.yml to run cargo bench on each commit
  • [ ] Consolidate and document the existing benchmark files in benches/ with clear comments about what each measures
  • [ ] Add a BENCHMARKING.md guide explaining how to run benchmarks locally and interpret results
  • [ ] Configure the workflow to comment on PRs with performance impact data (using a tool like criterion-compare)

Add integration tests for the docs/playground WebAssembly build

The docs/playground directory contains a WASM project (playground.wasm, playground_bg.wasm) and Cargo.toml, but there's no CI workflow testing that the playground builds successfully or validating the WASM compilation. The existing ci.yml only runs tests on the main tera crate. This is critical because playground failures would break the interactive documentation at keats.github.io/tera/.

  • [ ] Create a new step in .github/workflows/ci.yml or a separate workflow to build the WASM playground with wasm-pack build
  • [ ] Add wasm-pack as a dev dependency or document the toolchain requirement in a CONTRIBUTING.md file
  • [ ] Add a test in docs/playground/tests/web.rs that validates the playground can parse and render a simple template
  • [ ] Ensure the workflow fails if WASM compilation fails, preventing broken playground releases

Document and add missing filter/function feature tests for optional dependencies

The Cargo.toml shows several optional features (slug, urlencode, humansize, chrono, chrono-tz, rand) but there's no visible test module structure ensuring each feature-gated filter/function is tested. Given the freeze of v1 and the migration to v2, adding feature-specific tests ensures v1 stability for users still depending on it.

  • [ ] Add a tests/ directory (if not present) with feature-specific test files: test_slug_filter.rs, test_chrono_filter.rs, test_urlencode_filter.rs, etc.
  • [ ] Ensure each test file uses #[cfg(feature = "...")] to only compile when the feature is enabled
  • [ ] Update .github/workflows/ci.yml to run cargo test --all-features and cargo test --no-default-features separately
  • [ ] Add a table in CONTRIBUTING.md documenting which filters require which features (slug→slugify, chrono→date filter, etc.)

🌿Good first issues

  • Add missing tests for edge cases in the regex-based filters (striptags, spaceless, titles) in benches/escaping.rs and equivalent unit test coverage, since these are critical for HTML safety.
  • Document the Pest grammar used for template parsing (likely in src/ or inline) in a dedicated docs/GRAMMAR.md file, since new contributors often struggle with parser rules and this is mentioned in the README but not detailed.
  • Expand docs/content/docs/ with explicit examples for each built-in filter when optional features are disabled, since the current docs assume builtins are enabled and don't show fallback behavior clearly.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 998057c — Update README (Keats)
  • 447c210 — Update documentation by removing strict mode mention (#988) (mollerhoj)
  • 2a3913e — fix: Prevent the default filter from escaping it's argument twice (#970) (mousetail)
  • 14262cd — Fix panic when formatting timestamp with timezone specifier (#983) (klevente)
  • 4e5145e — 1.20.1 (Keats)
  • bae8291 — refactor: 📦 Replace unmaintained unic-segment dependency (#979) (berkus)
  • af0b32f — Add small note about child templates ignoring code outside of a block (#964) (Raymi306)
  • ae13d7c — Update changelog for 1.20.0 (#922) (orhun)
  • c05bff1 — Update version number (Keats)
  • 0d5de55 — Get ready for release (#920) (Keats)

🔒Security observations

The Tera template engine codebase demonstrates reasonable security practices with no critical vulnerabilities identified. The main concerns are: (1) potential template injection risks inherent to template engines requiring careful usage documentation, (2) some dependency versions that should be kept updated (chrono, regex), and (3) global state management (lazy_static) that needs careful review for thread-safety in web contexts. The project has fuzz testing infrastructure but could expand coverage. Overall security posture is moderate-to-good, with most risks being usage-dependent rather than implementation

  • Medium · Outdated Dependency: chrono 0.4.27 — Cargo.toml - chrono dependency. The chrono dependency is pinned to version 0.4.27, which may contain known security vulnerabilities. Chrono has had multiple CVEs in the past, including denial of service issues. The dependency should be updated to the latest patched version. Fix: Update chrono to the latest stable version (0.4.x or later) to receive security patches. Consider using chrono = "0.4" without version pinning to allow patch updates.
  • Medium · Potential Template Injection Vulnerability — src/ - core template processing logic. As a template engine, Tera processes user-provided templates. The codebase structure suggests extensive template parsing via the pest parser. Without explicit documentation on input sanitization and sandboxing, there's a risk of template injection attacks if user input is directly compiled as templates without proper validation. Fix: Implement strict input validation for template sources. Provide clear documentation on safe usage patterns. Consider implementing a restricted/safe mode that disables dangerous operations (e.g., arbitrary code execution). Perform security audits of the pest grammar rules.
  • Medium · Regex Dependency Usage Without Version Pinning — Cargo.toml - regex dependency and src/builtins/filters/. The regex crate (1.7) is used in multiple filters (striptags, spaceless, titles, truncate) but the version constraint allows updates to 1.x. While generally safe, regex implementations can have ReDoS (Regular Expression Denial of Service) vulnerabilities. The current pinning is loose. Fix: Regularly update the regex dependency to the latest version. Consider adding regex pattern validation/limits to prevent ReDoS attacks if user-provided patterns are processed. Document any regex limitations.
  • Low · Lazy Static Usage — Cargo.toml - lazy_static dependency. The codebase uses lazy_static for global state initialization. While not inherently insecure, improper use of global state in multi-threaded contexts can lead to race conditions or state leakage between requests in web server contexts. Fix: Review all lazy_static usages to ensure thread-safe initialization and no cross-request state pollution. Consider migrating to once_cell (std library in newer Rust versions) which has better semantics. Ensure template caching doesn't leak sensitive data between requests.
  • Low · Missing Cargo.lock in Repository — Repository root. The repository does not appear to include a Cargo.lock file (not listed in the file structure), which means transitive dependencies may vary across installations. This increases supply chain risk for applications depending on Tera. Fix: Commit Cargo.lock to version control to ensure reproducible builds and consistent dependency versions across all installations of Tera.
  • Low · Fuzz Testing Limited Coverage — fuzz/fuzz_targets/. While fuzz targets exist for conditions, expressions, and templates, the coverage may not be comprehensive for all filter functions and edge cases. This could allow bugs to slip through. Fix: Expand fuzz testing to cover all filters (string, array, object, number filters). Add fuzzing for context handling and recursive template resolution. Integrate continuous fuzzing via OSS-Fuzz.
  • Low · Unicode Segmentation Dependency — Cargo.toml and src/builtins/filters/string.rs. The unicode-segmentation crate (1.12) is used for string iteration in truncate filter. While generally safe, ensure proper handling of multi-byte characters in security-sensitive contexts. Fix: Verify that truncation operations don't create invalid UTF-8 or truncate in the middle of multi-byte sequences, which could cause security issues if outputs are used in command execution or injection contexts.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · Keats/tera — RepoPilot