Guide for rewriting Jinja2 templates in Kida syntax.
:::note[Not a Drop-In Replacement] Kida is not compatible with Jinja2. Templates must be rewritten using Kida syntax. This guide helps you translate common patterns. :::
Prerequisites
- Python 3.14+
- Familiarity with Jinja2 template syntax
Step 1: Install Kida
pip install kida-templates
Step 2: Update Imports
Before (Jinja2):
from jinja2 import Environment, FileSystemLoader
from markupsafe import Markup
After (Kida):
from kida import Environment, FileSystemLoader
from kida import Markup # Built-in, no markupsafe
Step 3: Update Environment Creation
Before (Jinja2):
env = Environment(
loader=FileSystemLoader('templates'),
autoescape=select_autoescape(),
extensions=['jinja2.ext.do'],
)
After (Kida):
env = Environment(
loader=FileSystemLoader('templates'),
autoescape=True,
cache_size=400,
fragment_ttl=300.0,
)
Note: Kida does not use Jinja2-style extensions. For side effects, use {% set _ = expr %}.
Step 4: Keep Matching Block Endings
No migration edit is required for matching explicit closers on constructs Kida supports. The following Jinja source is valid in Kida unchanged:
{% if condition %}
content
{% endif %}
{% for item in items %}
{{ item }}
{% endfor %}
{% block content %}
...
{% endblock %}
Unified {% end %}remains canonical Kida style, so you may normalize the same
template later as an optional style cleanup:
{% if condition %}
content
{% end %}
{% for item in items %}
{{ item }}
{% end %}
{% block content %}
...
{% end %}
Step 5: Update Custom Filters
Before (Jinja2):
def format_money(value, currency='$'):
return f'{currency}{value:,.2f}'
env.filters['money'] = format_money
After (Kida):
def format_money(value, currency='$'):
return f'{currency}{value:,.2f}'
env.add_filter('money', format_money)
# Or use the decorator
@env.filter()
def format_money(value, currency='$'):
return f'{currency}{value:,.2f}'
Step 6: Update Custom Tests
Before (Jinja2):
def is_prime(n):
return n > 1 and all(n % i for i in range(2, n))
env.tests['prime'] = is_prime
After (Kida):
env.add_test('prime', is_prime)
# Or use the decorator
@env.test()
def is_prime(n):
return n > 1 and all(n % i for i in range(2, n))
API Mapping
| Jinja2 | Kida |
|---|---|
Environment |
Environment |
FileSystemLoader |
FileSystemLoader |
DictLoader |
DictLoader |
Template.render() |
Template.render() |
Template.render_async() |
Template.render_async() |
Markup(markupsafe) |
Markup(built-in) |
env.filters[name] = func |
env.add_filter(name, func) |
env.tests[name] = func |
env.add_test(name, func) |
env.globals[name] = value |
env.add_global(name, value) |
Syntax Translation
| Jinja2 | Kida |
|---|---|
{% endif %} |
Accepted unchanged;{% end %}is the canonical alternative |
{% endfor %} |
Accepted unchanged;{% end %}is the canonical alternative |
{% endblock %} |
Accepted unchanged;{% end %}is the canonical alternative |
{% macro name(...) %} |
{% def name(...) %} (Kida has no {% macro %}keyword) |
{% endmacro %} |
{% end %} |
{{ x \| filter }} |
{{ x \|> filter }}(pipeline) |
{% if %}...{% elif %}...{% endif %} |
{% match %}...{% case %}...{% end %} |
| N/A | {% cache key %}...{% end %} |
Audit assignments that cross anifblock
Jinja2 lets an assignment inside{% if %}update the surrounding scope;
Kida keeps{% set %} block-scoped. kida checkreports K-WARN-002 when it
can prove that such a binding is read after the block:
{% if user %}
{% set greeting = "Hello " ~ user.name %}
{% endif %}
{{ greeting }}
Use {% let greeting = ... %}when the name is first introduced and should
be template-wide. Use{% export greeting = ... %}when updating an existing
template-wide binding. Assignments do not escape{% for %}in either engine;
anif-local assignment read later in the same iteration can still be reported
with loop-local restructuring advice. K-WARN-002 is advisory only because
control flow, shadowing, and dynamic values can make an automatic rewrite
unsound.
New Features
After migrating, explore Kida-only features:
Pipeline Operator
{{ title |> escape |> upper |> truncate(50) }}
Pattern Matching
{% match status %}
{% case "active" %}
✓ Active
{% case "pending" %}
⏳ Pending
{% case _ %}
Unknown
{% end %}
Block Caching
{% cache "sidebar-" ~ user.id %}
{{ render_sidebar(user) }}
{% end %}
Verification
After migration, verify templates render correctly:
from kida import Environment
env = Environment()
template = env.from_string("Hello, {{ name }}!")
result = template.render(name="World")
assert result == "Hello, World!"
print("✅ Migration successful!")
Common Issues
Markup Import
Error:ImportError: cannot import name 'Markup' from 'markupsafe'
Fix: Import from Kida:
from kida import Markup # Not from markupsafe
Filter Registration
Error:TypeError: 'FilterRegistry' object does not support item assignment
Fix: Useadd_filter():
env.add_filter('name', func) # Not env.filters['name'] = func
Block Endings
Matching explicit closers are accepted. If Kida reports a block-ending error, check that the closer matches the open construct. Both of these are valid:
{% if x %}...{% endif %}
{% if x %}...{% end %} {# canonical Kida style #}
Do not blanket-rewrite endif, endfor, or endblockduring migration.
See Also
- Kida Components vs Jinja2 Macros — Side-by-side component comparison
- Components Guide — Kida component patterns
- Control Flow — Pattern matching syntax
- Comparison — Kida vs Jinja2
- Performance — Benchmark results