23 KiB
version, date, author, title, description, summary, quick_tips
version | date | author | title | description | summary | quick_tips | |||
---|---|---|---|---|---|---|---|---|---|
1.0 | 2025-01-15 | DWS Foldsite Team | Template Helper Functions | Complete reference for all Jinja2 helper functions in Foldsite | Comprehensive API documentation for Foldsite's template helper functions - discover content, navigate your site, and build dynamic features. |
|
Template Helper Functions
Foldsite provides powerful helper functions you can use in any template. These functions access your content dynamically, enabling features like recent posts, navigation menus, breadcrumbs, and more.
Content Discovery
get_folder_contents(path)
Get all files and folders in a directory with rich metadata.
Parameters:
path
(str, optional) - Relative path from content root. Defaults to current directory.
Returns: List of TemplateFile
objects with attributes:
name
(str) - Filename with extensionpath
(str) - Relative path from content rootproper_name
(str) - Filename without extensionextension
(str) - File extension (.md
,.jpg
, etc.)categories
(list[str]) - File categories (['document']
,['image']
, etc.)date_modified
(str) - Last modified date (YYYY-MM-DD
)date_created
(str) - Creation date (YYYY-MM-DD
)size_kb
(int) - File size in kilobytesmetadata
(dict | None) - Markdown frontmatter or image EXIF datadir_item_count
(int) - Number of items if it's a directoryis_dir
(bool) - True if it's a directory
Example:
<h2>Files in this folder:</h2>
<ul>
{% for file in get_folder_contents(currentPath) %}
<li>
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
({{ file.size_kb }} KB, modified {{ file.date_modified }})
</li>
{% endfor %}
</ul>
Sort and filter:
{# Sort by date, newest first #}
{% for file in get_folder_contents()|sort(attribute='date_created', reverse=True) %}
...
{% endfor %}
{# Filter to only documents #}
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
{% endif %}
{% endfor %}
get_sibling_content_files(path)
Get files in the same directory as the current page.
Parameters:
path
(str, optional) - Relative path. Defaults to current directory.
Returns: List of tuples (filename, relative_path)
Example:
<nav class="sibling-nav">
<h3>Other pages in this section:</h3>
{% for name, path in get_sibling_content_files(currentPath) %}
<a href="/{{ path }}"
{% if path == currentPath %}class="active"{% endif %}>
{{ name }}
</a>
{% endfor %}
</nav>
get_sibling_content_folders(path)
Get folders in the same directory as the current page.
Parameters:
path
(str, optional) - Relative path. Defaults to current directory.
Returns: List of tuples (folder_name, relative_path)
Example:
<nav class="folder-nav">
<h3>Sections:</h3>
{% for name, path in get_sibling_content_folders(currentPath) %}
<a href="/{{ path }}">{{ name }}</a>
{% endfor %}
</nav>
get_text_document_preview(path)
Get a preview (first 100 characters) of a text document.
Parameters:
path
(str) - Relative path to the document
Returns: String (first 100 characters of the file)
Example:
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}
<article>
<h3><a href="/{{ file.path }}">{{ file.proper_name }}</a></h3>
<p>{{ get_text_document_preview(file.path) }}...</p>
</article>
{% endif %}
{% endfor %}
get_rendered_markdown(path)
Get fully rendered markdown content without Jinja2 templating. Perfect for displaying markdown files (like index.md
) within folder views or embedding content from one markdown file into a template.
Parameters:
path
(str) - Relative path to the markdown file
Returns: Dictionary with:
html
(str | None) - Rendered HTML content from markdownmetadata
(dict | None) - Frontmatter metadata from the markdown fileexists
(bool) - True if file was found and rendered successfully
Example:
{# Display index.md in a folder view #}
{% set index_path = (currentPath + '/index.md') if currentPath else 'index.md' %}
{% set index = get_rendered_markdown(index_path) %}
{% if index.exists %}
<section class="folder-index">
{{ index.html | safe }}
{% if index.metadata.author %}
<p class="author">By {{ index.metadata.author }}</p>
{% endif %}
</section>
{% endif %}
Use in folder templates:
{# Show index.md content at the top of a folder listing #}
<div class="folder-view">
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
<div class="folder-introduction">
{{ index.html | safe }}
<hr>
</div>
{% endif %}
{# Then show other files in the folder #}
<div class="folder-contents">
{% for file in get_folder_contents(currentPath) %}
{% if file.name != 'index.md' %}
<a href="/{{ file.path }}">{{ file.proper_name }}</a>
{% endif %}
{% endfor %}
</div>
</div>
Embed content from another file:
{# Include shared content from another markdown file #}
{% set about = get_rendered_markdown('about/team-bio.md') %}
{% if about.exists %}
<aside class="team-bio">
{{ about.html | safe }}
</aside>
{% endif %}
get_markdown_metadata(path)
Get metadata (frontmatter) from a markdown file without rendering the content. Perfect for displaying static markdown metadata in any location - like showing post titles, descriptions, or custom fields without the overhead of rendering the full markdown.
Parameters:
path
(str) - Relative path to the markdown file
Returns: Dictionary with:
metadata
(dict | None) - Frontmatter metadata from the markdown fileexists
(bool) - True if file was found successfullyerror
(str, optional) - Error message if reading failed
Example:
{# Display metadata from a specific post without rendering it #}
{% set post_meta = get_markdown_metadata('blog/my-awesome-post.md') %}
{% if post_meta.exists %}
<div class="featured-post">
<h2>{{ post_meta.metadata.title }}</h2>
<p class="description">{{ post_meta.metadata.description }}</p>
<p class="date">Published: {{ post_meta.metadata.date }}</p>
<div class="tags">
{% for tag in post_meta.metadata.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
<a href="/blog/my-awesome-post.md">Read more →</a>
</div>
{% endif %}
Build a custom post grid using metadata only:
{# Create cards showing metadata from multiple posts #}
<div class="post-grid">
{% for post_path in ['blog/intro.md', 'blog/tutorial.md', 'blog/advanced.md'] %}
{% set meta = get_markdown_metadata(post_path) %}
{% if meta.exists %}
<article class="post-card">
<h3>{{ meta.metadata.title }}</h3>
<time datetime="{{ meta.metadata.date }}">{{ meta.metadata.date }}</time>
{% if meta.metadata.author %}
<p class="author">By {{ meta.metadata.author }}</p>
{% endif %}
{% if meta.metadata.excerpt %}
<p>{{ meta.metadata.excerpt }}</p>
{% endif %}
<a href="/{{ post_path }}">Read full post</a>
</article>
{% endif %}
{% endfor %}
</div>
Show custom metadata fields:
{# Display reading time, difficulty, or any custom frontmatter field #}
{% set meta = get_markdown_metadata('tutorials/python-basics.md') %}
{% if meta.exists %}
<div class="tutorial-info">
<h2>{{ meta.metadata.title }}</h2>
{% if meta.metadata.difficulty %}
<span class="badge">{{ meta.metadata.difficulty }}</span>
{% endif %}
{% if meta.metadata.reading_time %}
<span class="time">⏱ {{ meta.metadata.reading_time }} min read</span>
{% endif %}
{% if meta.metadata.prerequisites %}
<div class="prerequisites">
<strong>Prerequisites:</strong>
<ul>
{% for prereq in meta.metadata.prerequisites %}
<li>{{ prereq }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
{% endif %}
Performance tip: Use get_markdown_metadata
instead of get_rendered_markdown
when you only need frontmatter data. It's much faster since it doesn't process the markdown content.
Blog Functions
get_recent_posts(limit, folder)
Get recent blog posts sorted by date (newest first).
Parameters:
limit
(int, optional) - Maximum number of posts. Default: 5folder
(str, optional) - Search within specific folder. Default: "" (search everywhere)
Returns: List of post dictionaries with:
title
(str) - From frontmatter or filenamedate
(str) - From frontmatterpath
(str) - Relative path to posturl
(str) - Full URL to postmetadata
(dict) - Full frontmatter including tags, description, etc.
Example:
<section class="recent-posts">
<h2>Recent Posts</h2>
{% for post in get_recent_posts(limit=5, folder='blog') %}
<article>
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<time datetime="{{ post.date }}">{{ post.date }}</time>
{% if post.metadata.description %}
<p>{{ post.metadata.description }}</p>
{% endif %}
{% if post.metadata.tags %}
<div class="tags">
{% for tag in post.metadata.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</section>
Search in specific folder:
{# Only posts from blog/tutorials/ #}
{% for post in get_recent_posts(limit=10, folder='blog/tutorials') %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
get_posts_by_tag(tag, limit)
Get posts filtered by a specific tag.
Parameters:
tag
(str) - Tag to filter by (case-insensitive)limit
(int, optional) - Maximum posts. Default: 10
Returns: List of post dictionaries (same format as get_recent_posts
)
Example:
<h2>Python Posts</h2>
{% for post in get_posts_by_tag('python', limit=10) %}
<article>
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<time>{{ post.date }}</time>
</article>
{% endfor %}
Dynamic tag pages:
{# If currentPath is 'tags/python.md' #}
{% set tag = currentPath.split('/')[-1].replace('.md', '') %}
<h1>Posts tagged: {{ tag }}</h1>
{% for post in get_posts_by_tag(tag) %}
...
{% endfor %}
get_related_posts(current_post_path, limit)
Find posts related to the current post based on shared tags.
Parameters:
current_post_path
(str) - Path to current postlimit
(int, optional) - Maximum related posts. Default: 3
Returns: List of post dictionaries with an additional overlap_score
field (number of shared tags)
Example:
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
<aside class="related-posts">
<h3>You might also like:</h3>
{% for post in related %}
<article>
<a href="{{ post.url }}">{{ post.title }}</a>
<small>{{ post.overlap_score }} shared tags</small>
</article>
{% endfor %}
</aside>
{% endif %}
get_all_tags()
Get all tags used across the site with post counts.
Returns: List of tag dictionaries with:
name
(str) - Tag namecount
(int) - Number of posts with this tag
Example:
<div class="tag-cloud">
{% for tag in get_all_tags() %}
<a href="/tags/{{ tag.name|lower }}"
class="tag"
style="font-size: {{ 0.8 + (tag.count * 0.1) }}em;">
{{ tag.name }} ({{ tag.count }})
</a>
{% endfor %}
</div>
Sort by popularity:
{% for tag in get_all_tags()|sort(attribute='count', reverse=True) %}
<a href="/tags/{{ tag.name|lower }}">
{{ tag.name }} <span class="count">{{ tag.count }}</span>
</a>
{% endfor %}
Navigation
generate_breadcrumbs(current_path)
Generate breadcrumb navigation based on URL path.
Parameters:
current_path
(str) - Current page path
Returns: List of breadcrumb dictionaries with:
title
(str) - Display title (from metadata or derived from path)url
(str) - URL to this levelis_current
(bool) - True if this is the current page
Example:
<nav class="breadcrumbs" aria-label="Breadcrumb">
{% for crumb in generate_breadcrumbs(currentPath) %}
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% else %}
<span aria-current="page">{{ crumb.title }}</span>
{% endif %}
{% if not loop.last %} / {% endif %}
{% endfor %}
</nav>
Styled example:
<ol class="breadcrumb">
{% for crumb in generate_breadcrumbs(currentPath) %}
<li class="breadcrumb-item {% if crumb.is_current %}active{% endif %}">
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% else %}
{{ crumb.title }}
{% endif %}
</li>
{% endfor %}
</ol>
get_navigation_items(max_items)
Get top-level pages and folders for site navigation.
Parameters:
max_items
(int, optional) - Maximum items to return. Default: 10
Returns: List of navigation dictionaries with:
title
(str) - Display title (from metadata or filename)url
(str) - URL to page/folderpath
(str) - Relative pathis_folder
(bool, optional) - True if it's a folder
Example:
<nav class="main-nav">
<a href="/">Home</a>
{% for item in get_navigation_items(max_items=10) %}
<a href="{{ item.url }}"
{% if currentPath.startswith(item.path) %}class="active"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>
With icons for folders:
{% for item in get_navigation_items() %}
<a href="{{ item.url }}">
{% if item.is_folder %}📁{% endif %}
{{ item.title }}
</a>
{% endfor %}
Gallery Functions
get_photo_albums()
Get all photo gallery directories (folders containing mostly images).
Returns: List of album dictionaries with:
name
(str) - Album namepath
(str) - Relative pathurl
(str) - Full URLimage_count
(int) - Number of imagestotal_files
(int) - Total files in album
Example:
<div class="photo-albums">
<h2>Photo Galleries</h2>
{% for album in get_photo_albums() %}
<a href="{{ album.url }}" class="album-card">
<h3>{{ album.name }}</h3>
<p>{{ album.image_count }} photos</p>
</a>
{% endfor %}
</div>
Built-in Jinja2 Filters
In addition to Foldsite helpers, you can use standard Jinja2 filters:
String Filters
{{ "hello"|upper }} {# HELLO #}
{{ "HELLO"|lower }} {# hello #}
{{ "hello world"|title }} {# Hello World #}
{{ "hello world"|capitalize }} {# Hello world #}
{{ " text "|trim }} {# text #}
{{ "hello"|reverse }} {# olleh #}
List Filters
{{ items|length }} {# Count items #}
{{ items|first }} {# First item #}
{{ items|last }} {# Last item #}
{{ items|join(', ') }} {# Join with comma #}
{{ items|sort }} {# Sort list #}
{{ items|sort(attribute='date') }} {# Sort by attribute #}
{{ items|reverse }} {# Reverse list #}
{{ items|unique }} {# Remove duplicates #}
Other Useful Filters
{{ value|default('N/A') }} {# Default if falsy #}
{{ html|safe }} {# Don't escape HTML #}
{{ number|round(2) }} {# Round to 2 decimals #}
{{ date|replace('-', '/') }} {# Replace strings #}
Common Patterns
Pattern: Blog Homepage with Recent Posts
<main>
<h1>Welcome to My Blog</h1>
<section class="recent-posts">
{% for post in get_recent_posts(limit=10) %}
<article class="post-card">
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<time datetime="{{ post.date }}">{{ post.date }}</time>
{% if post.metadata.description %}
<p>{{ post.metadata.description }}</p>
{% endif %}
{% if post.metadata.tags %}
<div class="tags">
{% for tag in post.metadata.tags %}
<a href="/tags/{{ tag|lower }}" class="tag">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</section>
<aside class="sidebar">
<h3>Popular Tags</h3>
<div class="tag-cloud">
{% for tag in get_all_tags()|sort(attribute='count', reverse=True)[:10] %}
<a href="/tags/{{ tag.name|lower }}">{{ tag.name }} ({{ tag.count }})</a>
{% endfor %}
</div>
</aside>
</main>
Pattern: Folder Index with Previews
<div class="folder-index">
<h1>{{ currentPath.split('/')[-1]|title }}</h1>
<nav class="breadcrumbs">
{% for crumb in generate_breadcrumbs(currentPath) %}
{% if not crumb.is_current %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a> /
{% else %}
{{ crumb.title }}
{% endif %}
{% endfor %}
</nav>
{# Show index.md content if it exists #}
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
<section class="folder-introduction">
{{ index.html | safe }}
<hr>
</section>
{% endif %}
<div class="content-grid">
{% for file in get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
{% if 'document' in file.categories and file.name != 'index.md' %}
<div class="content-card">
<h3><a href="/{{ file.path }}">{{ file.proper_name }}</a></h3>
<p class="date">{{ file.date_created }}</p>
{% if file.metadata and file.metadata.description %}
<p>{{ file.metadata.description }}</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
Pattern: Photo Gallery with EXIF Data
<div class="gallery">
{% set photos = get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
<h1>{{ currentPath.split('/')[-1]|title }}</h1>
<p>{{ photos|length }} photos</p>
<div class="photo-grid">
{% for photo in photos %}
{% if 'image' in photo.categories %}
<figure class="photo">
<a href="/download/{{ photo.path }}">
<img src="/download/{{ photo.path }}?max_width=600"
alt="{{ photo.name }}"
loading="lazy">
</a>
<figcaption>
{% if photo.metadata and photo.metadata.exif %}
<p>{{ photo.metadata.exif.DateTimeOriginal }}</p>
{% if photo.metadata.exif.Model %}
<p>{{ photo.metadata.exif.Model }}</p>
{% endif %}
{% endif %}
</figcaption>
</figure>
{% endif %}
{% endfor %}
</div>
</div>
Pattern: Documentation with Sibling Pages
<article class="doc-page">
<nav class="doc-sidebar">
<h3>In This Section:</h3>
<ul>
{% for name, path in get_sibling_content_files(currentPath) %}
<li>
<a href="/{{ path }}"
{% if path == currentPath %}class="active"{% endif %}>
{{ name.replace('.md', '')|replace('-', ' ')|title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
<div class="doc-content">
<nav class="breadcrumbs">
{% for crumb in generate_breadcrumbs(currentPath) %}
<a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% if not loop.last %} › {% endif %}
{% endfor %}
</nav>
{{ content|safe }}
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
<aside class="related-docs">
<h4>Related Documentation:</h4>
<ul>
{% for doc in related %}
<li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
{% endfor %}
</ul>
</aside>
{% endif %}
</div>
</article>
Performance Tips
Caching
Most helper functions are cached automatically. It's safe to call them multiple times:
{# These don't cause multiple filesystem scans #}
{% set posts = get_recent_posts(limit=5) %}
... use posts ...
{% set posts_again = get_recent_posts(limit=5) %} {# Cached result #}
Filtering vs. Multiple Calls
Filter results in templates rather than calling helpers multiple times:
✓ Efficient:
{% set all_files = get_folder_contents() %}
{% set docs = all_files|selectattr('categories', 'contains', 'document') %}
{% set images = all_files|selectattr('categories', 'contains', 'image') %}
✗ Less efficient:
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}...{% endif %}
{% endfor %}
{% for file in get_folder_contents() %} {# Second call #}
{% if 'image' in file.categories %}...{% endif %}
{% endfor %}
Debugging
Enable debug mode to see which templates and helpers are being used:
# config.toml
[server]
debug = true
Add debug output to templates:
{# Show what get_recent_posts returns #}
{% set posts = get_recent_posts(limit=3) %}
<pre>{{ posts|pprint }}</pre>
{# Show current variables #}
<pre>
currentPath: {{ currentPath }}
metadata: {{ metadata|pprint }}
</pre>
Next Steps
- Template Recipes - See these helpers in complete working examples
- Template System Overview - Understand how templates work
- Template Discovery - Learn how Foldsite finds templates
With these helpers, you can build sophisticated, dynamic sites while keeping your content as simple files and folders!