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.
Example: Basic Usage
{# Filter by exact value (default) #}
{% let tutorials = site.pages |> where('category', 'tutorial') %}
{# Filter by nested attribute #}
{% let track_pages = site.pages |> where('metadata.track_id', 'getting-started') %}
Example: 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') |
Example: Operator Examples
{# Pages newer than a year ago #}
{% let recent = site.pages |> where('date', one_year_ago, 'gt') %}
{# Pages with priority 5 or higher #}
{% let important = site.pages |> where('metadata.priority', 5, 'gte') %}
{# Pages tagged with 'python' #}
{% let python_posts = site.pages |> where('tags', 'python', 'in') %}
{# Pages with specific statuses #}
{% let active = site.pages |> where('status', ['active', 'featured'], 'in') %}
{# Exclude archived pages #}
{% let 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 #}
{% let published = site.pages |> where_not('draft', true) %}
{# Exclude archived items #}
{% let active = users |> where_not('status', 'archived') %}
sort_by
Sort items by a key, with optional reverse order.
{# Sort by date, newest first #}
{% let recent = site.pages |> sort_by('date', reverse=true) %}
{# Sort alphabetically by title #}
{% let alphabetical = site.pages |> sort_by('title') %}
{# Sort by weight (ascending) #}
{% let ordered = sections |> sort_by('weight') %}
group_by
Group items by a key value, returning a dictionary.
{% let 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>
{% end %}
</ul>
{% end %}
group_by_year
Group pages by publication year. Returns dictionary sorted by year (newest first).
{% let by_year = site.pages |> group_by_year %}
{% for year, posts in by_year.items() %}
<h2>{{ year }}</h2>
<ul>
{% for post in posts %}
<li><a href="{{ post.href }}">{{ post.title }}</a></li>
{% end %}
</ul>
{% end %}
Parameters:
date_attr: Attribute containing the date (default:'date')
group_by_month
Group pages by year-month. Returns dictionary keyed by(year, month)tuples.
{% let by_month = site.pages |> group_by_month %}
{% for (year, month), posts in by_month.items() %}
<h2>{{ month | month_name }} {{ year }}</h2>
<ul>
{% for post in posts %}
<li><a href="{{ post.href }}">{{ post.title }}</a></li>
{% end %}
</ul>
{% end %}
archive_years
Get list of years with post counts for archive navigation.
{% let years = site.pages |> archive_years %}
<aside class="archive">
<h3>Archive</h3>
<ul>
{% for item in years %}
<li>
<a href="/blog/{{ item.year }}/">{{ item.year }}</a>
<span>({{ item.count }})</span>
</li>
{% end %}
</ul>
</aside>
Returns: List of dicts withyearandcountkeys, sorted newest first.
limit
Take the first N items from a list.
{# Latest 5 posts #}
{% let latest = site.pages |> sort_by('date', reverse=true) |> limit(5) %}
{# Top 3 featured items #}
{% let featured = items |> where('featured', true) |> limit(3) %}
offset
Skip the first N items from a list.
{# Skip first 10 items (pagination page 2) #}
{% let page_2 = items |> offset(10) |> limit(10) %}
{# Skip the featured post #}
{% let rest = posts |> offset(1) %}
first
Get the first item from a list, orNoneif empty.
{# Get the featured post #}
{% let featured = site.pages |> where('metadata.featured', true) |> first %}
{% if featured %}
<div class="hero">
<h1>{{ featured.title }}</h1>
</div>
{% end %}
last
Get the last item from a list, orNoneif empty.
{# Get the oldest post #}
{% let oldest = site.pages |> sort_by('date') |> last %}
{# Get the final step #}
{% let final_step = steps |> last %}
reverse
Reverse a list (returns a new list, original unchanged).
{# Oldest first #}
{% let chronological = site.pages |> sort_by('date') %}
{# Newest first (reversed) #}
{% let newest_first = chronological |> reverse %}
uniq
Remove duplicate items while preserving order.
{# Get unique tags from all posts #}
{% let all_tags = [] %}
{% for page in site.pages %}
{% let all_tags = all_tags + page.tags %}
{% end %}
{% let unique_tags = all_tags | uniq %}
flatten
Flatten nested lists into a single list.
{# Combine all tags from all pages #}
{% let nested_tags = site.pages |> map(attribute='tags') |> list %}
{% let 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 #}
{% let featured = site.pages |> where('metadata.featured', true) %}
{% let recent = site.pages |> sort_by('date', reverse=true) |> limit(5) %}
{% let combined = featured |> union(recent) %}
intersect
Get items that appear in both lists.
{# Posts that are both featured AND tagged 'python' #}
{% let featured = site.pages |> where('metadata.featured', true) %}
{% let python = site.pages |> where('tags', 'python', 'in') %}
{% let featured_python = featured |> intersect(python) %}
complement
Get items in the first list that are NOT in the second list.
{# All posts except featured ones #}
{% let all_posts = site.pages |> where('type', 'post') %}
{% let featured = site.pages |> where('metadata.featured', true) %}
{% let regular = all_posts |> complement(featured) %}
Chaining Filters
Filters can be chained for powerful queries:
{# Recent Python tutorials, sorted by date #}
{% let 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:
{% let posts = site.pages |> where('section', 'blog') %}
{% let 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:
{% let featured = site.pages |> where('metadata.featured', true) |> first %}
{% let 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:
{% let both = list1 |> intersect(list2) %}
{% let combined = list1 |> union(list2) %}
{% let diff = list1 |> complement(list2) %}
Tag Filtering
Hugo:
{{ $tagged := where .Site.RegularPages "Params.tags" "intersect" (slice "python" "web") }}
Bengal:
{# Check if page has 'python' tag #}
{% let tagged = site.pages |> where('tags', 'python', 'in') %}
{# Check if page has any of these tags #}
{% let tagged = site.pages |> where('tags', ['python', 'web'], 'in') %}
Complex Queries
Hugo:
{{ $result := where (where .Site.RegularPages "Section" "blog") ".Params.featured" true }}
Bengal:
{% let 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) |