Rendering Contexts

Passing variables and context to templates

3 min read 628 words

Pass data to templates through the rendering context.

Basic Rendering

Pass variables as keyword arguments:

template = env.get_template("page.html")
html = template.render(
    title="My Page",
    user=current_user,
    items=item_list,
)

Or as a dictionary:

context = {
    "title": "My Page",
    "user": current_user,
    "items": item_list,
}
html = template.render(context)

Convenience Methods

Environment provides shortcuts:

# Combines get_template() + render()
html = env.render("page.html", title="Hello")

# Combines from_string() + render()
html = env.render_string("{{ x * 2 }}", x=21)

Global Variables

Variables available in all templates:

env = Environment(loader=FileSystemLoader("templates/"))

# Add globals
env.add_global("site_name", "My Site")
env.add_global("current_year", 2024)
env.add_global("format_date", format_date_func)

Access in templates:

<title>{{ site_name }}</title>
<footer>&copy; {{ current_year }}</footer>
{{ format_date(post.date) }}

Built-in Globals

Kida includes common Python builtins:

{{ range(10) }}
{{ len(items) }}
{{ dict(a=1, b=2) }}
{{ max(scores) }}

Available: range, dict, list, set, tuple, len, str, int, float, bool, abs, min, max, sum, sorted, reversed, enumerate, zip, map, filter.

Object Access

Templates access object attributes:

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

template.render(user=User("Alice", "alice@example.com"))
{{ user.name }}
{{ user.email }}

Dictionary Access

template.render(config={"timeout": 30, "retries": 3})
{{ config.timeout }}
{{ config["timeout"] }}

Both syntaxes work. For dicts, Kida resolves dot notation to dictionary keys first, so `` is equivalent to config["timeout"]. This means dict key names like items, keys, or values resolve to your data, not the dictmethods:

template.render(section={"items": ["a", "b", "c"], "title": "My Section"})
{% for item in section.items %}    {# iterates ["a", "b", "c"], not dict.items() #}
    {{ item }}
{% end %}

Nested Contexts

Build complex nested data:

template.render(
    site={
        "title": "My Site",
        "nav": [
            {"title": "Home", "url": "/"},
            {"title": "About", "url": "/about"},
        ],
    },
    page={
        "title": "Welcome",
        "content": "Hello, world!",
    },
)
<title>{{ page.title }} - {{ site.title }}</title>

<nav>
{% for item in site.nav %}
    <a href="{{ item.url }}">{{ item.title }}</a>
{% end %}
</nav>

Undefined Variables

By default, undefined variables raise errors:

template = env.from_string("{{ missing }}")
template.render()  # Raises UndefinedError

Use defaultfilter for optional values:

{{ user.nickname | default("Anonymous") }}
{{ config.timeout | default(30) }}

Context Isolation

Eachrender()call starts with a fresh context:

# These don't affect each other
html1 = template.render(x=1)
html2 = template.render(x=2)

Globals are shared but render context is isolated.

Clean User Context

Your context dictionary remains completely clean—no internal keys are injected:

ctx = {"name": "World", "items": [1, 2, 3]}
template.render(ctx)

# ctx is unchanged after render()
# No _template, _line, _include_depth keys added
assert "_template" not in ctx

Internal state (template name, line number for errors, include depth) is managed via RenderContext ContextVar, not your dictionary. This means you can safely use variable names like _template or _linein your templates:

{{ _template }}  {# Works! Your variable, not internal state #}

Best Practices

Keep Context Flat

# ✅ Flat, easy to access
template.render(
    title=page.title,
    user=current_user,
    items=items,
)

# ❌ Deeply nested
template.render(
    data={
        "page": {"meta": {"title": ...}},
        ...
    }
)

Use Typed Objects

# ✅ IDE support, validation
@dataclass
class PageContext:
    title: str
    user: User
    items: list[Item]

template.render(**asdict(PageContext(...)))

Precompute in Python

# ✅ Python handles complexity
template.render(
    formatted_items=[format_item(i) for i in items],
    total=sum(i.price for i in items),
)

# ❌ Complex logic in template
# {% set total = 0 %}
# {% for item in items %}{% set total = total + item.price %}{% end %}

See Also