Kida Syntax Reference (Detailed)

Exhaustive Kida filter, operator, caching, and migration reference

12 min read 2470 words
Edit this page

Was this page helpful?

Lookup-oriented reference for built-in filters, caching, operators, tests, configuration, and Jinja2 migration patterns.

Note

Do I need this? Use when you know basic Kida syntax and need a specific filter or operator. For syntax fundamentals, read Kida Syntax first.

Built-in Filters

String Filters

KIDA
{{ text | upper }}
{{ text | lower }}
{{ text | capitalize }}
{{ text | title }}
{{ text | trim }}
{{ text | truncate(100) }}
{{ text | truncatewords(20) }}
{{ text | replace('old', 'new') }}
{{ text | slugify }}

{# Prefix/Suffix Operations #}
{{ url | trim_prefix("https://") }}    {# Remove prefix #}
{{ file | trim_suffix(".txt") }}       {# Remove suffix #}
{{ url | has_prefix("https://") }}     {# Check prefix (bool) #}
{{ file | has_suffix(".md") }}         {# Check suffix (bool) #}
{{ text | contains("error") }}         {# Check substring (bool) #}

{# Regex Extraction #}
{{ "Price: $99.99" | regex_search(r'\$[\d.]+') }}  {# "$99.99" #}
{{ "v2.3.1" | regex_search(r'v(\d+)', group=1) }} {# "2" #}
{{ "a1 b2 c3" | regex_findall(r'\d+') }}           {# ["1", "2", "3"] #}

{# Natural Language #}
{{ ["Alice", "Bob", "Charlie"] | to_sentence }}    {# "Alice, Bob, and Charlie" #}
{{ items | to_sentence(connector='or') }}          {# "A, B, or C" #}

{# Base64 Encoding #}
{{ "hello" | base64_encode }}     {# "aGVsbG8=" #}
{{ encoded | base64_decode }}     {# Decode Base64 #}

Collection Filters

KIDA
{{ items |> first }}
{{ items |> last }}
{{ items | length }}
{{ items |> sort }}
{{ items |> reverse }}
{{ items |> unique }}
{{ items | join(', ') }}
{{ items |> group_by('category') }}

Type Conversion

KIDA
{{ value | string }}
{{ value | int }}
{{ value | float }}
{{ value | bool }}
{{ value | list }}
{{ value | dict }}
{{ value | tojson }}

HTML/Security

KIDA
{{ html | safe }}      {# Render raw HTML #}
{{ html | escape }}    {# Escape HTML #}
{{ html | e }}         {# Short form #}
{{ html | striptags }} {# Remove HTML tags #}
{{ html | plainify }}  {# Alias for striptags (Hugo compat) #}

{# Auto-link URLs #}
{{ "Visit https://example.com" | urlize }}
{# Output: Visit <a href="https://example.com">https://example.com</a> #}

{{ text | urlize(target='_blank', rel='noopener') }}  {# Open in new tab #}

Validation

KIDA
{{ value | default('fallback') }}
{{ value | d('fallback') }}      {# Short form #}
{{ value | require }}            {# Raise if None #}

URL Manipulation

KIDA
{# Parse URL into components #}
{% let parts = url | url_parse %}
{{ parts.scheme }}     {# "https" #}
{{ parts.host }}       {# "example.com" #}
{{ parts.path }}       {# "/docs/api" #}
{{ parts.query }}      {# "version=2" #}
{{ parts.fragment }}   {# "section" #}
{{ parts.params.q }}   {# ["search term"] #}

{# Extract query parameter #}
{{ "https://x.com?page=2" | url_param('page') }}        {# "2" #}
{{ url | url_param('missing', 'default') }}             {# "default" #}

{# Build query string from dict #}
{{ {'q': 'test', 'page': 1} | url_query }}              {# "q=test&page=1" #}

Date Filters

KIDA
{# Format dates #}
{{ page.date | dateformat('%B %d, %Y') }}  {# "January 15, 2026" #}
{{ page.date | dateformat('%Y-%m-%d') }}   {# "2026-01-15" (default) #}
{{ page.date | date_iso }}                 {# ISO 8601 format #}
{{ page.date | date_rfc822 }}              {# RFC 822 for RSS feeds #}

{# Relative time #}
{{ post.date | time_ago }}    {# "2 days ago", "5 hours ago" #}
{{ post.date | days_ago }}    {# 14 (integer) #}
{{ post.date | months_ago }}  {# 3 (integer) #}
{% if post.date | days_ago < 7 %}NEW{% end %}

{# Add/subtract time #}
{{ page.date | date_add(days=7) }}               {# One week later #}
{{ now | date_add(days=-30) }}                   {# 30 days ago #}
{{ event.start | date_add(hours=2, minutes=30) }} {# 2.5 hours later #}
{{ page.date | date_add(weeks=2) }}              {# Two weeks later #}

{# Calculate difference #}
{{ end_date | date_diff(start_date) }}               {# Days between #}
{{ end_date | date_diff(start_date, unit='hours') }} {# Hours between #}
{{ end_date | date_diff(start_date, unit='all') }}   {# Dict with all units #}

Fragment Caching

Kida provides built-in fragment caching:

KIDA
{% cache "sidebar-nav" %}
  {{ build_nav_tree(site.pages) }}
{% end %}

{% cache "weather", ttl="5m" %}
  {{ fetch_weather() }}
{% end %}

{% cache "posts-" ~ site.nav_version %}
  {% for post in recent_posts %}
    {{ post.title }}
  {% end %}
{% end %}

Cache keys can be:

  • Static strings:"sidebar"
  • Expressions:"posts-" ~ page.id
  • Variables:cache_key

TTL (Time To Live):

  • ttl="5m"- 5 minutes
  • ttl="1h"- 1 hour
  • ttl="30s"- 30 seconds

Automatic Block Caching

Kida automatically caches site-scoped template blocks for optimal build performance. This happens automatically—no template syntax changes required.

How it works:

  1. Analysis: Kida analyzes your templates to identify blocks that only depend on site-wide context (not page-specific data)
  2. Pre-rendering: These blocks are rendered once at build start
  3. Automatic reuse: During page rendering, cached blocks are used automatically instead of re-rendering

Example:

KIDA
{# base.html #}
{% block nav %}
  <nav>
    {% for page in site.pages %}
      <a href="{{ page.href }}">{{ page.title }}</a>
    {% end %}
  </nav>
{% end %}

{% block content %}
  {{ page.content | safe }}
{% end %}

The nav block depends only on site.pages (site-wide), so it's automatically cached and reused for all pages. The content block depends on page.content(page-specific), so it renders per page.

Benefits:

  • Significantly faster builds for navigation-heavy sites (benchmarks show 10x+ improvement for large sites)
  • Zero template changes — works automatically
  • Transparent — templates render normally, caching is invisible

Cache scope detection:

  • Site-scoped: Blocks that only accesssite.*, config.*, or no page-specific variables
  • Page-scoped: Blocks that accesspage.*or other page-specific data
  • Not cacheable: Blocks with non-deterministic behavior (random, shuffle, etc.)

Tip

Performance tip: Structure your templates so site-wide blocks (nav, footer, sidebar) are separate from page-specific blocks (content). Kida will automatically optimize them.

Kida-Only Features

These features are unique to Kida and not available in Jinja2:

Feature Syntax Purpose
Optional chaining ?., ?[ Safe access without errors
Null coalescing ?? Fallback for None values
Range literals 1..10, 1...11 Concise iteration ranges
While loops {% while %} Condition-based iteration
Break/Continue {% break %}, {% continue %} Loop control

Optional Chaining

Safe navigation operators returnNoneinstead of raising errors when accessing missing attributes or keys.

Optional Attribute Access (?.)

Safe attribute access—returnsNone if the object is None:

KIDA
{{ user?.profile?.name }}           {# None if user or profile is None #}
{{ page?.metadata?.author }}        {# None if page or metadata is None #}
{{ config?.theme?.colors?.primary }} {# Deep safe access #}

Optional Subscript Access (?[)

Safe key/index access—returnsNone if the object is None:

KIDA
{{ data?['key'] }}                  {# None if data is None #}
{{ schema?['in'] }}                 {# Access reserved word keys safely #}
{{ items?[0] }}                     {# Safe index access #}
{{ config?['settings']?['theme'] }} {# Chained safe subscript #}

Combining with attribute access:

KIDA
{{ api?.response?['data']?['items'] }}  {# Mix both operators #}
{{ user?.preferences?['theme'] ?? 'light' }}

Tip

Kida uses?[ not ?.[]: Unlike JavaScript which uses ?.['key'], Kida uses the more concise ?['key']. The pattern is simple: prefix ?makes any accessor optional.

Accessor Regular Optional
Attribute .attr ?.attr
Subscript ['key'] ?['key']

Common Use Cases

Accessing reserved word keys (common in OpenAPI, JSON Schema):

KIDA
{# OpenAPI security schemes use 'in' as a field name #}
{% let location = scheme?['in'] ?? 'header' %}
{% let api_type = spec?['type'] ?? 'apiKey' %}

Safe array access:

KIDA
{% let first = items?[0] %}
{% let last = items?[-1] %}
{{ results?[0]?.name ?? 'No results' }}

Nested data structures:

KIDA
{% let value = response?['data']?['users']?[0]?['email'] %}
{{ value ?? 'Not found' }}

Null Coalescing (??)

Fallback for None values:

KIDA
{{ page.subtitle ?? page.title }}
{{ user.name ?? 'Guest' }}

Warning

Precedence gotcha: The?? operator has lower precedence than |(pipe). This means filters bind to the fallback value, not the result:

KIDA
{# ❌ Wrong: parses as items ?? ([] | length) #}
{{ items ?? [] | length }}

{# ✅ Correct: use default filter for filter chains #}
{{ items | default([]) | length }}

{# ✅ Also works: explicit parentheses #}
{{ (items ?? []) | length }}

When to Use Each Pattern

Pattern Use When Example
?? Simple fallback, no filtering {{ user.name ?? 'Anonymous' }}
| default() Fallback followed by filters {{ items \| default([]) \| length }}

Rule of thumb: If you need to apply filters after the fallback, use| default(). Otherwise, ??is fine.

Range Literals

KIDA
{% for i in 1..10 %}
  {{ i }}  {# 1, 2, 3, ..., 10 #}
{% end %}

{% for i in 1...11 %}
  {{ i }}  {# 1, 2, 3, ..., 10 (exclusive end) #}
{% end %}

{% for i in 1..10 by 2 %}
  {{ i }}  {# 1, 3, 5, 7, 9 #}
{% end %}

While Loops

Condition-based loops for scenarios where the iteration count isn't known upfront:

KIDA
{% let counter = 0 %}
{% while counter < 5 %}
  {{ counter }}
  {% set counter = counter + 1 %}
{% end %}

With break and continue:

KIDA
{% let i = 0 %}
{% while true %}
  {% if i >= 10 %}{% break %}{% end %}
  {% set i = i + 1 %}
  {% if i % 2 == 0 %}{% continue %}{% end %}
  {{ i }}  {# Prints odd numbers: 1, 3, 5, 7, 9 #}
{% end %}

Tip

While loops are useful for:

  • Processing data until a condition is met
  • Implementing search algorithms in templates
  • Building countdown/countup displays

Break and Continue

KIDA
{% for item in items %}
  {% if item.hidden %}
    {% continue %}
  {% end %}
  {% if item.stop %}
    {% break %}
  {% end %}
  {{ item.title }}
{% end %}

Spaceless

Remove whitespace between HTML tags:

KIDA
{% spaceless %}
  <div>
    <span>Hello</span>
    <span>World</span>
  </div>
{% endspaceless %}
{# Output: <div><span>Hello</span><span>World</span></div> #}

Operators

Arithmetic

KIDA
{{ 10 + 5 }}    {# 15 #}
{{ 10 - 5 }}    {# 5 #}
{{ 10 * 5 }}    {# 50 #}
{{ 10 / 5 }}    {# 2.0 #}
{{ 10 // 5 }}   {# 2 #}
{{ 10 % 3 }}    {# 1 #}
{{ 2 ** 3 }}    {# 8 #}

Comparison

KIDA
{{ a == b }}     {# Equal #}
{{ a != b }}     {# Not equal #}
{{ a < b }}      {# Less than #}
{{ a > b }}      {# Greater than #}
{{ a <= b }}     {# Less than or equal #}
{{ a >= b }}     {# Greater than or equal #}
{{ a in b }}     {# Membership #}
{{ a not in b }} {# Not in #}

Logical

KIDA
{{ a and b }}
{{ a or b }}
{{ not a }}

String Concatenation

KIDA
{{ "Hello" ~ " " ~ "World" }}  {# "Hello World" #}
{{ name ~ "-" ~ id }}           {# "john-123" #}

Tests

KIDA
{% if value is defined %}
  {{ value }}
{% end %}

{% if value is none %}
  Default value
{% end %}

{% if value is even %}
  Even number
{% end %}

{% if value is odd %}
  Odd number
{% end %}

{% if value is divisibleby(3) %}
  Divisible by 3
{% end %}

Error Handling

Strict Mode (Always On)

Kida raisesUndefinedErrorfor undefined variables. Strict mode is always enabled and cannot be disabled — this helps catch typos and missing context variables at render time.

KIDA
{{ missing_var }}  {# Raises UndefinedError #}
{{ missing_var | default('N/A') }}  {# Safe: 'N/A' #}

Configuration

Kida is Bengal's default template engine. Configure inbengal.toml or config/_default/:

YAML
kida:
  bytecode_cache: true      # Persistent compiled template cache (default)
  static_context: false     # Opt-in compile-time folding for config.* branches
  template_aliases:         # Optional @alias/ include roots
    components: ui/components
Option Default Description
bytecode_cache true Cache compiled templates to disk
static_context false Fold constantconfig.*expressions at compile time
template_aliases unset Map@alias/prefixes to template-root-relative paths

Switching template engines:

YAML
site:
  template_engine: jinja2   # Options: kida (default), jinja2, mako

Migration from Jinja2

Kida can parse Jinja2 syntax via compatibility mode. Most Jinja2 templates work without changes, but consider migrating to Kida syntax for pattern matching, pipeline operators, and unified block endings:

Seealso

How to Migrate from Jinja2 to Kida — Step-by-step migration guide

Key differences:

  1. Block endings: Replace{% endif %}, {% endfor %} with {% end %}
  2. Template variables: Use{% let %} instead of {% set %}for template-wide scope
  3. Pattern matching: Replace longif/elif chains with {% match %}...{% case %}
  4. Pipeline: Use|>for better readability

Complete Example

KIDA
{% extends "baseof.html" %}

{% let site_title = site.config.title %}
{% let recent_posts = site.pages
  |> where('type', 'blog')
  |> where('draft', false)
  |> sort_by('date', reverse=true)
  |> take(5) %}

{% block content %}
  <h1>{{ site_title }}</h1>

  {% match page.type %}
    {% case "blog" %}
      <article class="blog-post">
        <h2>{{ page.title }}</h2>
        <time>{{ page.date | dateformat('%B %d, %Y') }}</time>
        {{ page.content | safe }}
      </article>
    {% case "list" %}
      <ul>
        {% for post in recent_posts %}
          <li>
            <a href="{{ post.url }}">{{ post.title }}</a>
            <span>{{ post.date | days_ago }} days ago</span>
          </li>
        {% end %}
      </ul>
    {% case _ %}
      <div>{{ page.content | safe }}</div>
  {% end %}

  {% cache "sidebar-" ~ site.nav_version %}
    <aside>
      {{ build_sidebar(site.pages) }}
    </aside>
  {% end %}
{% end %}

See Also

See Also