Kida adds modern operators for data transformation and null-safe access.
Pipeline Operator (|>)
The pipeline operator chains filters left-to-right:
{% let recent_posts = site.pages
|> where('type', 'blog')
|> where('draft', false)
|> sort_by('date', reverse=true)
|> take(10) %}
Pipeline vs Pipe
Both|and|>compile to identical code:
{# These are functionally identical #}
{{ items |> where('published', true) |> sort_by('date') |> take(5) }}
{{ items | where('published', true) | sort_by('date') | take(5) }}
| Syntax | Convention |
|---|---|
| |
Inline expressions, Jinja2 familiarity |
|> |
Multiline pipelines, functional style |
Note
You cannot mix|and|>in the same expression. Pick one per chain.
Multiline Pipelines
Add inline comments to explain each step:
{% let recent_posts = site.pages
|> where('type', 'blog') {# Only blog posts #}
|> where('draft', false) {# Exclude drafts #}
|> sort_by('date', reverse=true) {# Newest first #}
|> take(10) %} {# Limit to 10 #}
Filter Name Mapping
Common Jinja2 patterns and their Kida equivalents:
| Jinja2 Filter | Kida Filter | Description |
|---|---|---|
selectattr('key') |
where('key', true) |
Boolean filter |
selectattr('key', 'eq', val) |
where('key', val) |
Equality filter |
rejectattr('key') |
where_not('key', true) |
Inverse boolean |
sort(attribute='key') |
sort_by('key') |
Sort by attribute |
batch(n) | first |
take(n) |
Get first n items (both return a list of n items;take()avoids grouping overhead) |
groupby('key') |
group_by('key') |
Group by attribute |
Note
Aboutbatch(n) | firstvstake(n): While both return the first n items,batch(n)groups all items into batches first, thenfirstextracts the first batch. Thetake(n)filter directly takes the first n items without grouping, making it more efficient for this use case.
Optional Chaining (?.)
Safe navigation through potentially null values:
{{ user?.profile?.name ?? 'Anonymous' }}
{{ page?.metadata?.author?.avatar }}
{{ config?.social?.twitter?.handle }}
Compare to Jinja2's defensive coding:
{{ user.profile.name if user and user.profile and user.profile.name else 'Anonymous' }}
Null Coalescing (??)
Concise fallback for null/undefined values:
{{ page.subtitle ?? page.title }}
{{ user.nickname ?? user.name ?? 'Guest' }}
{{ config.theme ?? 'default' }}
Precedence Gotcha
The??operator has lower precedence than|, so filters bind to the fallback:
{# ❌ Parses as: items ?? ([] | length) — returns list, not length! #}
{{ items ?? [] | length }}
{# ✅ Correct: use | default() for filter chains #}
{{ items | default([]) | length }}
Rule: Use??for simple direct output. Use| default()when applying filters after the fallback.
Combined Usage
{{ page?.metadata?.image ?? site?.config?.default_image ?? '/images/placeholder.png' }}
Best Practices
Filter Early, Limit Early
{# ✅ Good: Filter before sorting #}
{% let posts = site.pages
|> where('type', 'blog')
|> where('draft', false)
|> sort_by('date')
|> take(10) %}
{# ❌ Less efficient: Sort everything first #}
{% let posts = site.pages
|> sort_by('date')
|> where('type', 'blog')
|> take(10) %}
Use Multiline for Complex Chains
{# 3+ filters: use multiline #}
{% let posts = site.pages
|> where('type', 'blog')
|> sort_by('date', reverse=true)
|> take(5) %}
{# Simple chains: inline is fine #}
{{ items |> take(3) }}
Complete Example
{% extends "baseof.html" %}
{% let post = page %}
{% let author = post?.metadata?.author ?? site?.config?.default_author ?? 'Anonymous' %}
{% let post_image = post?.metadata?.image ?? post?.metadata?.cover %}
{% let related_posts = site.pages
|> where('type', 'blog')
|> where('tags', post.tags | first)
|> where_not('_path', post._path)
|> sort_by('date', reverse=true)
|> take(3) %}
{% block content %}
<article class="blog-post">
{% if post_image %}
<img src="{{ post_image }}" alt="{{ post.title }}">
{% end %}
<header>
<h1>{{ post.title }}</h1>
<span>By {{ author }}</span>
</header>
{{ post.content | safe }}
{% if related_posts | length > 0 %}
<aside class="related">
<h2>Related Posts</h2>
{% for item in related_posts %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% end %}
</aside>
{% end %}
</article>
{% endblock %}