Filters & Globals

Register custom template filters and globals, and use Chirp's web-specific built-ins

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

A filter transforms a value inside a template with the pipe syntax —{{ value | filter }}. A global is a function or value available in every template without passing it through the route's context. Chirp ships a handful of web-specific built-in filters and globals, and lets you register your own with @app.template_filter() and @app.template_global().

Register a custom filter

Decorate a function with@app.template_filter(). The function name becomes the filter name, and the piped value is the first argument:

from chirp import App

app = App()

@app.template_filter()
def currency(value: float) -> str:
    return f"${value:,.2f}"

Use it in a template with the pipe:

<span class="price">{{ product.price | currency }}</span>
{# → <span class="price">$1,299.00</span> #}

Extra arguments after the piped value are passed in the parentheses:

@app.template_filter()
def excerpt(value: str, length: int = 50, suffix: str = "...") -> str:
    if len(value) <= length:
        return value
    return value[:length].rsplit(" ", 1)[0] + suffix
<p>{{ post.body | excerpt(120) }}</p>

Filters are ordinary Python functions, so your type annotations give your IDE autocomplete and type-checking on the arguments.

Common patterns

Named filters

By default the function name is the filter name. Pass a string to override it — useful when the Python name and the template name should differ:

@app.template_filter("type_color")
def type_color(type_name: str) -> str:
    """Return the CSS color for a Pokemon type."""
    return TYPE_COLORS.get(type_name.lower(), "#777")


@app.template_filter("sprite")
def sprite_url(pokemon_id: int, variant: str = "default") -> str:
    """Return the PokeAPI sprite URL for a Pokemon ID."""
    if variant == "artwork":
        return f"{SPRITE_BASE}/other/official-artwork/{pokemon_id}.png"
    return f"{SPRITE_BASE}/{pokemon_id}.png"

Source: examples/standalone/pokedex/app.py.

<span style="color: {{ pokemon.type | type_color }}">{{ pokemon.type }}</span>
<img src="{{ pokemon.id | sprite('artwork') }}" alt="{{ pokemon.name }}">

Template globals

Globals are functions or values callable from any template without being passed in the route context:

from datetime import datetime

@app.template_global()
def site_name() -> str:
    return "My App"

@app.template_global()
def current_year() -> int:
    return datetime.now().year
<footer>&copy; {{ current_year() }} {{ site_name() }}</footer>

Like filters, globals take an optional name argument: @app.template_global("year").

Built-in filters

Chirp registers these web-specific filters on every template environment — no import or registration needed. They complement the filters that come from Kida, the template engine.

Filter Does Example
field_errors Returns the list of validation messages for one form field, or an empty list when there are none. {% for m in errors | field_errors("email") %}…{% end %}
qs Builds a query string on a URL path. Falsy values (None, "", 0, False) are dropped. {{ '/search' | qs(q=query, page=page) }}/search?q=hello&page=2
attr Emits an HTML attribute only when the value is truthy, else nothing. The value is HTML-escaped. <a href="/x"{{ cls | attr("class") }}> class="active" when clsis truthy
url Safelists a URL for anhref: returns it when the scheme is safe (http, https, relative), else a fallback (default #). Use on user or external data. <a href="{{ link | url(fallback='/') }}">

field_errorspairs with the errors dict returned by form validation:

<label>Email</label>
<input name="email" value="{{ form.email ?? "" }}">
{% for msg in errors | field_errors("email") %}
    <span class="field-error">{{ msg }}</span>
{% end %}

Escaping and thesafefilter

WhenAppConfig(autoescape=True) (the default), {{ x }} is HTML-escaped automatically — the main defense against XSS. The escaping filters come from Kida: use | e (or | escape) to escape explicitly when chaining filters that might drop escaping, and | safe(reason="...")to mark output as trusted HTML so it is not escaped.

To render markdown, register themarkdown filter once during setup. It sanitizes unsafe HTML and URLs by default; pass sanitize=Falseonly for fully trusted markdown:

from chirp.markdown import register_markdown_filter

register_markdown_filter(app)   # adds the `markdown` filter
{{ post.body | markdown }}

Next steps

  • Rendering — how a template is rendered into a response
  • App lifecycle — when filters and globals are frozen into the environment
  • API reference — the complete API surface