DebugUndefinedErrorexceptions.
:::tip[Upgrading from 0.6.x?]
strict_undefined=True became the default in 0.7.0. If you are hitting a wave of UndefinedErrorexceptions after an upgrade, see Upgrade to 0.7 for the three fix patterns and the escape hatch.
:::
The Error
UndefinedError: Undefined variable 'usre' in page.html:5
Recent Kida diagnostics also carry structured fields for frameworks:
to_diagnostic().location— template name, line, and optional columnto_diagnostic().source_snippet— surrounding source linesto_diagnostic().hints— ordered next actionsto_diagnostic().template_stackand.component_stack— render path context
Common Causes
Typo in variable name
{# ❌ Typo #}
{{ usre.name }}
{# ✅ Correct #}
{{ user.name }}
Check spelling against what's passed to render().
Variable not passed to template
# ❌ Missing variable
template.render(title="Hello")
# ✅ Include all needed variables
template.render(title="Hello", user=current_user)
Ensure all template variables are passed in render().
Wrong attribute name
{# ❌ Wrong attribute #}
{{ user.nmae }}
{# ✅ Correct attribute #}
{{ user.name }}
Verify object attributes match your code.
Imported component slot uses a missing caller value
{% from "components/card.html" import card %}
{% call card() %}
{{ missing_in_slot }}
{% end %}
Kida reports this against the caller template line that owns the slot body, not the imported component file. Use the component stack to see which component was rendering when the slot failed.
Nested object is None or missing
{# ❌ page.parent might be None or missing #}
{{ page.parent.title }}
{# ✅ Guard with `is defined` (works for attribute chains) #}
{% if page.parent is defined and page.parent %}
{{ page.parent.title }}
{% end %}
{# ✅ Or use the null-coalescing operator #}
{{ page.parent.title ?? "" }}
Under strict mode (the default), {% if page.parent %} alone raises if parent is missing — use is defined or ??.
Solutions
Hints are ordered by confidence. A close typo match appears first; optional
value patterns such asdefault(...), ??, or null-safe access follow.
Use default Filter
{{ user.nickname | default("Anonymous") }}
{{ config.timeout | default(30) }}
Check with is defined
Theis defined test works on attribute chains, not just top-level variables. If any part of the chain is missing, the result is undefined:
{% if user is defined %}
{{ user.name }}
{% else %}
Guest
{% end %}
Attribute Chains
{# Checks if pokemon has a "name" attribute — not just if pokemon exists #}
{% if pokemon.name is defined %}
{{ pokemon.name }}
{% end %}
{# Works with dict keys too #}
{% if settings.theme is defined %}
Theme: {{ settings.theme }}
{% end %}
{# Deep chains #}
{% if page.author.avatar is defined %}
<img src="{{ page.author.avatar }}">
{% end %}
Undefined Sentinel (lenient mode only)
Withstrict_undefined=False (opt-in), missing attribute access returns an _Undefinedsentinel. The sentinel is:
- Falsy —
{% if pokemon.name %}works as a guard - Stringifies to
""—{{ pokemon.name }}renders nothing when undefined - Iterable — yields nothing, so
{% for x in missing_attr %}produces no output
Under the default strict mode, missing attributes raiseUndefinedError. Use is defined, ??, or | default(...)to opt specific sites into lenient behavior. See Tests Reference for the full test list.
Optional Chaining Pattern
{% if post is defined and post.author is defined %}
{{ post.author.name }}
{% end %}
Safe Navigation
{{ user.name ?? "Unknown" }}
Prefer Null-Safe Operators
Understrict_undefined=True, reaching for Python's .get("k", "")inside templates adds noise at every call site. Kida ships with first-class null-safe operators — prefer these:
Optional Chaining —?. and ?[...](Mapping-soft, object-strict)
Since v0.8.0,?. and ?[...] short-circuit to Nonewhen either:
- The receiver is
Noneor undefined, or - The receiver is a Mapping (
dictorMappingsubclass) and the key is missing.
Missing attributes on a non-Mapping object still raise under strict mode — that's the typo-detection value ofstrict_undefined:
{# Receiver-None — yields "" #}
{{ config?.theme }} {# config = None → "" #}
{# Mapping miss — yields "" (dict.get() idiom) #}
{{ config?.theme }} {# config = {} → "" #}
{# Object attr miss — still raises, combine with ?? for safety #}
{{ user?.nickname ?? "" }} {# user = User() with no .nickname → "" #}
{# Safe patterns #}
{{ config?.theme ?? "dark" }}
{{ settings?["theme"] ?? "light" }}
| get(key, default) Filter — drop-in for dict.get
{# Handles None receiver, missing dict key, AND missing object attr uniformly #}
{{ config | get("theme", "light") | upper }}
Use | getwhen the lookup must be safe across all receiver shapes in one expression.
Chaining
{# Deep access with a named fallback #}
{{ user?.profile?.bio ?? "No bio yet" }}
{# Null-safe filter pipeline #}
{{ config ?| get("theme") ?? "light" }}
:::tip[Coming from Jinja2?]
Jinja2 lacks these operators, so a common Jinja2 pattern is{{ config.get("theme", "") | upper }} (using dict.get). That still works in Kida, but {{ config?.theme | upper }}is the preferred Kida idiom.
:::
Debug Tips
Print Available Variables
# In Python
print(context.keys())
Use debug Filter
{{ user | debug }}
Output (to stderr):
DEBUG: <User>
.name = 'Alice'
.email = 'alice@example.com'
Check Template Context
def render_debug(template_name, **context):
print(f"Rendering {template_name}")
print(f"Context keys: {list(context.keys())}")
return env.render(template_name, **context)
Strict Mode (Default)
As of 0.7.0,strict_undefined=True is the default. Missing variables and missing attributes raise UndefinedErrorwith a descriptive message distinguishing variable, attribute, and key lookups.
env = Environment(loader=FileSystemLoader("templates/"))
# strict_undefined=True by default
To guard optional access within a template, use one of:
{{ user.nickname ?? "Anonymous" }} {# null-coalescing #}
{{ user.nickname | default("Anonymous") }} {# default filter #}
{% if user.nickname is defined %}...{% end %} {# explicit test #}
Opt Out (Lenient Mode)
If you are porting templates that rely on silent empty-string fallback for missing attributes:
env = Environment(
loader=FileSystemLoader("templates/"),
strict_undefined=False,
)
In lenient mode, missing attributes return an _Undefinedsentinel (see above). This is recommended only as a transitional shim — prefer fixing sites with the idioms above.
Prevention
Type Hints for Context
from dataclasses import dataclass
@dataclass
class PageContext:
title: str
user: User
items: list[Item]
# IDE will catch missing fields
context = PageContext(title="Hello", user=user, items=items)
template.render(**asdict(context))
Template Validation
def validate_context(context, required):
missing = [k for k in required if k not in context]
if missing:
raise ValueError(f"Missing: {missing}")
See Also
- Error Handling — Exception types
- Variables — Variable access patterns
- Filters — The default filter