Bengal provides powerful template filters for querying, filtering, and transforming content collections. Many filters are inspired by Hugo for easy migration.
Collection Filters
These filters work with lists of pages, dictionaries, or any iterable.
where
Filter items where a key matches a value. Supports Hugo-like comparison operators.
Basic Usage:
| {# Filter by exact value (default) #}
{% set tutorials = site.pages | where('category', 'tutorial') %}
{# Filter by nested attribute #}
{% set track_pages = site.pages | where('metadata.track_id', 'getting-started') %}
|
With Comparison Operators:
| Operator |
Description |
Example |
eq |
Equal (default) |
where('status', 'published', 'eq') |
ne |
Not equal |
where('status', 'draft', 'ne') |
gt |
Greater than |
where('date', one_year_ago, 'gt') |
gte |
Greater than or equal |
where('priority', 5, 'gte') |
lt |
Less than |
where('weight', 100, 'lt') |
lte |
Less than or equal |
where('order', 10, 'lte') |
in |
Value in list |
where('tags', 'python', 'in') |
not_in |
Value not in list |
where('status', ['archived'], 'not_in') |
Operator Examples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | {# Pages newer than a year ago #}
{% set recent = site.pages | where('date', one_year_ago, 'gt') %}
{# Pages with priority 5 or higher #}
{% set important = site.pages | where('metadata.priority', 5, 'gte') %}
{# Pages tagged with 'python' #}
{% set python_posts = site.pages | where('tags', 'python', 'in') %}
{# Pages with specific statuses #}
{% set active = site.pages | where('status', ['active', 'featured'], 'in') %}
{# Exclude archived pages #}
{% set live = site.pages | where('status', ['archived', 'draft'], 'not_in') %}
|
where_not
Filter items where a key does NOT equal a value. Shorthand forwhere(key, value, 'ne').
| {# Exclude drafts #}
{% set published = site.pages | where_not('draft', true) %}
{# Exclude archived items #}
{% set active = users | where_not('status', 'archived') %}
|
sort_by
Sort items by a key, with optional reverse order.
| {# Sort by date, newest first #}
{% set recent = site.pages | sort_by('date', reverse=true) %}
{# Sort alphabetically by title #}
{% set alphabetical = site.pages | sort_by('title') %}
{# Sort by weight (ascending) #}
{% set ordered = sections | sort_by('weight') %}
|
group_by
Group items by a key value, returning a dictionary.
| {% set by_category = site.pages | group_by('category') %}
{% for category, pages in by_category.items() %}
<h2>{{ category }}</h2>
<ul>
{% for page in pages %}
<li><a href="{{ page.url }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% endfor %}
|
limit
Take the first N items from a list.
| {# Latest 5 posts #}
{% set latest = site.pages | sort_by('date', reverse=true) | limit(5) %}
{# Top 3 featured items #}
{% set featured = items | where('featured', true) | limit(3) %}
|
offset
Skip the first N items from a list.
| {# Skip first 10 items (pagination page 2) #}
{% set page_2 = items | offset(10) | limit(10) %}
{# Skip the featured post #}
{% set rest = posts | offset(1) %}
|
first
Get the first item from a list, orNoneif empty.
| {# Get the featured post #}
{% set featured = site.pages | where('metadata.featured', true) | first %}
{% if featured %}
<div class="hero">
<h1>{{ featured.title }}</h1>
</div>
{% endif %}
|
last
Get the last item from a list, orNoneif empty.
| {# Get the oldest post #}
{% set oldest = site.pages | sort_by('date') | last %}
{# Get the final step #}
{% set final_step = steps | last %}
|
reverse
Reverse a list (returns a new list, original unchanged).
| {# Oldest first #}
{% set chronological = site.pages | sort_by('date') %}
{# Newest first (reversed) #}
{% set newest_first = chronological | reverse %}
|
uniq
Remove duplicate items while preserving order.
| {# Get unique tags from all posts #}
{% set all_tags = [] %}
{% for page in site.pages %}
{% set all_tags = all_tags + page.tags %}
{% endfor %}
{% set unique_tags = all_tags | uniq %}
|
flatten
Flatten nested lists into a single list.
| {# Combine all tags from all pages #}
{% set nested_tags = site.pages | map(attribute='tags') | list %}
{% set all_tags = nested_tags | flatten | uniq %}
|
Set Operations
Perform set operations on lists of pages or items.
union
Combine two lists, removing duplicates.
| {# Combine featured and recent posts #}
{% set featured = site.pages | where('metadata.featured', true) %}
{% set recent = site.pages | sort_by('date', reverse=true) | limit(5) %}
{% set combined = featured | union(recent) %}
|
intersect
Get items that appear in both lists.
| {# Posts that are both featured AND tagged 'python' #}
{% set featured = site.pages | where('metadata.featured', true) %}
{% set python = site.pages | where('tags', 'python', 'in') %}
{% set featured_python = featured | intersect(python) %}
|
complement
Get items in the first list that are NOT in the second list.
| {# All posts except featured ones #}
{% set all_posts = site.pages | where('type', 'post') %}
{% set featured = site.pages | where('metadata.featured', true) %}
{% set regular = all_posts | complement(featured) %}
|
Chaining Filters
Filters can be chained for powerful queries:
| {# Recent Python tutorials, sorted by date #}
{% set result = site.pages
| where('category', 'tutorial')
| where('tags', 'python', 'in')
| where('draft', false)
| sort_by('date', reverse=true)
| limit(10) %}
|
Hugo Migration Guide
Bengal's template functions are designed for easy migration from Hugo. Here's how common Hugo patterns translate:
Filtering Pages
Hugo:
| {{ $posts := where .Site.RegularPages "Section" "blog" }}
{{ $recent := where .Site.RegularPages ".Date" ">" (now.AddDate -1 0 0) }}
|
Bengal:
| {% set posts = site.pages | where('section', 'blog') %}
{% set recent = site.pages | where('date', one_year_ago, 'gt') %}
|
Sorting
Hugo:
| {{ range .Site.RegularPages.ByDate.Reverse }}
{{ range sort .Site.RegularPages "Title" }}
|
Bengal:
| {% for page in site.pages | sort_by('date', reverse=true) %}
{% for page in site.pages | sort_by('title') %}
|
First/Last
Hugo:
| {{ $featured := (where .Site.RegularPages "Params.featured" true).First }}
{{ $oldest := .Site.RegularPages.ByDate.Last }}
|
Bengal:
| {% set featured = site.pages | where('metadata.featured', true) | first %}
{% set oldest = site.pages | sort_by('date') | last %}
|
Limiting
Hugo:
{{ range first 5 .Site.RegularPages }}
Bengal:
{% for page in site.pages | limit(5) %}
Set Operations
Hugo:
| {{ $both := intersect $list1 $list2 }}
{{ $combined := union $list1 $list2 }}
{{ $diff := complement $list1 $list2 }}
|
Bengal:
| {% set both = list1 | intersect(list2) %}
{% set combined = list1 | union(list2) %}
{% set diff = list1 | complement(list2) %}
|
Tag Filtering
Hugo:
{{ $tagged := where .Site.RegularPages "Params.tags" "intersect" (slice "python" "web") }}
Bengal:
| {# Check if page has 'python' tag #}
{% set tagged = site.pages | where('tags', 'python', 'in') %}
{# Check if page has any of these tags #}
{% set tagged = site.pages | where('tags', ['python', 'web'], 'in') %}
|
Complex Queries
Hugo:
{{ $result := where (where .Site.RegularPages "Section" "blog") ".Params.featured" true }}
Bengal:
{% set result = site.pages | where('section', 'blog') | where('metadata.featured', true) %}
Quick Reference
| Filter |
Purpose |
Example |
where(key, val) |
Filter by value |
pages \| where('type', 'post') |
where(key, val, 'gt') |
Greater than |
pages \| where('date', cutoff, 'gt') |
where(key, val, 'in') |
Value in list |
pages \| where('tags', 'python', 'in') |
where_not(key, val) |
Exclude value |
pages \| where_not('draft', true) |
sort_by(key) |
Sort ascending |
pages \| sort_by('title') |
sort_by(key, reverse=true) |
Sort descending |
pages \| sort_by('date', reverse=true) |
group_by(key) |
Group by value |
pages \| group_by('category') |
limit(n) |
Take first N |
pages \| limit(5) |
offset(n) |
Skip first N |
pages \| offset(10) |
first |
First item |
pages \| first |
last |
Last item |
pages \| last |
reverse |
Reverse order |
pages \| reverse |
uniq |
Remove duplicates |
tags \| uniq |
flatten |
Flatten nested lists |
nested \| flatten |
union(list2) |
Combine lists |
list1 \| union(list2) |
intersect(list2) |
Common items |
list1 \| intersect(list2) |
complement(list2) |
Difference |
list1 \| complement(list2) |
Navigation Functions
These global functions simplify common navigation patterns.
get_section
Get a section by its path. Cleaner alternative tosite.get_section_by_path().
| {% set docs = get_section('docs') %}
{% if docs %}
<h2>{{ docs.title }}</h2>
{% for page in docs.pages | sort_by('weight') %}
<a href="{{ page.url }}">{{ page.title }}</a>
{% endfor %}
{% endif %}
|
section_pages
Get pages from a section directly. Combinesget_section()with.pagesaccess.
| {# Non-recursive (direct children only) #}
{% for page in section_pages('docs') | sort_by('weight') %}
<a href="{{ page.url }}">{{ page.title }}</a>
{% endfor %}
{# Recursive (include all nested pages) #}
{% for page in section_pages('docs', recursive=true) %}
<a href="{{ page.url }}">{{ page.title }}</a>
{% endfor %}
|
page_exists
Check if a page exists without loading it. More efficient thanget_page()for conditional rendering.
| {% if page_exists('guides/advanced') %}
<a href="/guides/advanced/">Advanced Guide Available</a>
{% endif %}
{# Works with or without .md extension #}
{% if page_exists('docs/getting-started.md') %}...{% endif %}
{% if page_exists('docs/getting-started') %}...{% endif %}
|
String Filters
word_count
Count words in text, stripping HTML first. Uses same logic asreading_time.
| {{ page.content | word_count }} words
{# Combined with reading time #}
<span>{{ page.content | word_count }} words · {{ page.content | reading_time }} min read</span>
|
Also available aswordcount(Jinja naming convention).