Template Functions Reference

Complete reference for Bengal's template filters and functions

8 min read 1640 words

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:

1
2
3
4
5
{# 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').

1
2
3
4
5
{# 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.

1
2
3
4
5
6
7
8
{# 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{% 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.

1
2
3
4
5
{# 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.

1
2
3
4
5
{# 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.

1
2
3
4
5
6
7
8
{# 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.

1
2
3
4
5
{# 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).

1
2
3
4
5
{# Oldest first #}
{% set chronological = site.pages | sort_by('date') %}

{# Newest first (reversed) #}
{% set newest_first = chronological | reverse %}

uniq

Remove duplicate items while preserving order.

1
2
3
4
5
6
{# 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.

1
2
3
{# 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.

1
2
3
4
{# 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.

1
2
3
4
{# 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.

1
2
3
4
{# 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:

1
2
3
4
5
6
7
{# 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:

1
2
{{ $posts := where .Site.RegularPages "Section" "blog" }}
{{ $recent := where .Site.RegularPages ".Date" ">" (now.AddDate -1 0 0) }}

Bengal:

1
2
{% set posts = site.pages | where('section', 'blog') %}
{% set recent = site.pages | where('date', one_year_ago, 'gt') %}

Sorting

Hugo:

1
2
{{ range .Site.RegularPages.ByDate.Reverse }}
{{ range sort .Site.RegularPages "Title" }}

Bengal:

1
2
{% for page in site.pages | sort_by('date', reverse=true) %}
{% for page in site.pages | sort_by('title') %}

First/Last

Hugo:

1
2
{{ $featured := (where .Site.RegularPages "Params.featured" true).First }}
{{ $oldest := .Site.RegularPages.ByDate.Last }}

Bengal:

1
2
{% 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:

1
2
3
{{ $both := intersect $list1 $list2 }}
{{ $combined := union $list1 $list2 }}
{{ $diff := complement $list1 $list2 }}

Bengal:

1
2
3
{% 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:

1
2
3
4
5
{# 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)

These global functions simplify common navigation patterns.

get_section

Get a section by its path. Cleaner alternative tosite.get_section_by_path().

1
2
3
4
5
6
7
{% 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.

1
2
3
4
5
6
7
8
9
{# 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.

1
2
3
4
5
6
7
{% 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.

1
2
3
4
{{ 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).