---
version: "1.0"
date: "2025-01-15"
author: "DWS Foldsite Team"
title: "Template Helper Functions"
description: "Complete reference for all Jinja2 helper functions in Foldsite"
summary: "Comprehensive API documentation for Foldsite's template helper functions - discover content, navigate your site, and build dynamic features."
quick_tips:
- "All helpers are automatically available in every template"
- "Helpers return Python objects you can loop over and filter"
- "Most helpers are cached for performance - safe to call multiple times"
---
# 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 extension
- `path` (str) - Relative path from content root
- `proper_name` (str) - Filename without extension
- `extension` (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 kilobytes
- `metadata` (dict | None) - Markdown frontmatter or image EXIF data
- `dir_item_count` (int) - Number of items if it's a directory
- `is_dir` (bool) - True if it's a directory
**Example:**
```jinja
Files in this folder:
{% for file in get_folder_contents(currentPath) %}
```
**Sort and filter:**
```jinja
{# 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 %}
{{ file.proper_name }}
{% 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:**
```jinja
```
### 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:**
```jinja
```
### 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:**
```jinja
{% for file in get_folder_contents() %}
{% if 'document' in file.categories %}
{% 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 markdown
- `metadata` (dict | None) - Frontmatter metadata from the markdown file
- `exists` (bool) - True if file was found and rendered successfully
**Example:**
```jinja
{# 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 %}
{{ index.html | safe }}
{% if index.metadata.author %}
By {{ index.metadata.author }}
{% endif %}
{% endif %}
```
**Use in folder templates:**
```jinja
{# Show index.md content at the top of a folder listing #}
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
{{ index.html | safe }}
{% endif %}
{# Then show other files in the folder #}
{% for file in get_folder_contents(currentPath) %}
{% if file.name != 'index.md' %}
{{ file.proper_name }}
{% endif %}
{% endfor %}
```
**Embed content from another file:**
```jinja
{# Include shared content from another markdown file #}
{% set about = get_rendered_markdown('about/team-bio.md') %}
{% if about.exists %}
{% 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 file
- `exists` (bool) - True if file was found successfully
- `error` (str, optional) - Error message if reading failed
**Example:**
```jinja
{# Display metadata from a specific post without rendering it #}
{% set post_meta = get_markdown_metadata('blog/my-awesome-post.md') %}
{% if post_meta.exists %}
{{ post_meta.metadata.title }}
{{ post_meta.metadata.description }}
Published: {{ post_meta.metadata.date }}
{% for tag in post_meta.metadata.tags %}
{{ tag }}
{% endfor %}
{% endif %}
```
**Build a custom post grid using metadata only:**
```jinja
{# Create cards showing metadata from multiple posts #}
{% for post_path in ['blog/intro.md', 'blog/tutorial.md', 'blog/advanced.md'] %}
{% set meta = get_markdown_metadata(post_path) %}
{% if meta.exists %}
```
**Show custom metadata fields:**
```jinja
{# Display reading time, difficulty, or any custom frontmatter field #}
{% set meta = get_markdown_metadata('tutorials/python-basics.md') %}
{% if meta.exists %}
{{ meta.metadata.title }}
{% if meta.metadata.difficulty %}
{{ meta.metadata.difficulty }}
{% endif %}
{% if meta.metadata.reading_time %}
⏱ {{ meta.metadata.reading_time }} min read
{% endif %}
{% if meta.metadata.prerequisites %}
Prerequisites:
{% for prereq in meta.metadata.prerequisites %}
{{ prereq }}
{% endfor %}
{% endif %}
{% 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: 5
- `folder` (str, optional) - Search within specific folder. Default: "" (search everywhere)
**Returns:** List of post dictionaries with:
- `title` (str) - From frontmatter or filename
- `date` (str) - From frontmatter
- `path` (str) - Relative path to post
- `url` (str) - Full URL to post
- `metadata` (dict) - Full frontmatter including tags, description, etc.
**Example:**
```jinja
Recent Posts
{% for post in get_recent_posts(limit=5, folder='blog') %}
{% for tag in post.metadata.tags %}
{{ tag }}
{% endfor %}
{% endif %}
{% endfor %}
```
**Search in specific folder:**
```jinja
{# Only posts from blog/tutorials/ #}
{% for post in get_recent_posts(limit=10, folder='blog/tutorials') %}
{{ post.title }}
{% 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:**
```jinja
Python Posts
{% for post in get_posts_by_tag('python', limit=10) %}
{% endfor %}
```
**Dynamic tag pages:**
```jinja
{# If currentPath is 'tags/python.md' #}
{% set tag = currentPath.split('/')[-1].replace('.md', '') %}
Posts tagged: {{ tag }}
{% 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 post
- `limit` (int, optional) - Maximum related posts. Default: 3
**Returns:** List of post dictionaries with an additional `overlap_score` field (number of shared tags)
**Example:**
```jinja
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
{% endif %}
```
### get_all_tags()
Get all tags used across the site with post counts.
**Returns:** List of tag dictionaries with:
- `name` (str) - Tag name
- `count` (int) - Number of posts with this tag
**Example:**
```jinja
```
**Sort by popularity:**
```jinja
{% for tag in get_all_tags()|sort(attribute='count', reverse=True) %}
{{ tag.name }} {{ tag.count }}
{% 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 level
- `is_current` (bool) - True if this is the current page
**Example:**
```jinja
```
**Styled example:**
```jinja
{% for crumb in generate_breadcrumbs(currentPath) %}
{% if not crumb.is_current %}
{{ crumb.title }}
{% else %}
{{ crumb.title }}
{% endif %}
{% endfor %}
```
### 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/folder
- `path` (str) - Relative path
- `is_folder` (bool, optional) - True if it's a folder
**Example:**
```jinja
```
**With icons for folders:**
```jinja
{% for item in get_navigation_items() %}
{% if item.is_folder %}📁{% endif %}
{{ item.title }}
{% endfor %}
```
## Gallery Functions
### get_photo_albums()
Get all photo gallery directories (folders containing mostly images).
**Returns:** List of album dictionaries with:
- `name` (str) - Album name
- `path` (str) - Relative path
- `url` (str) - Full URL
- `image_count` (int) - Number of images
- `total_files` (int) - Total files in album
**Example:**
```jinja
{% for tag in post.metadata.tags %}
{{ tag }}
{% endfor %}
{% endif %}
{% endfor %}
```
### Pattern: Folder Index with Previews
```jinja
{{ currentPath.split('/')[-1]|title }}
{# Show index.md content if it exists #}
{% set index = get_rendered_markdown(currentPath + '/index.md') %}
{% if index.exists %}
{{ index.html | safe }}
{% endif %}
{% for file in get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
{% if 'document' in file.categories and file.name != 'index.md' %}
{% if file.metadata and file.metadata.description %}
{{ file.metadata.description }}
{% endif %}
{% endif %}
{% endfor %}
```
### Pattern: Photo Gallery with EXIF Data
```jinja
{% set photos = get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
{{ currentPath.split('/')[-1]|title }}
{{ photos|length }} photos
{% for photo in photos %}
{% if 'image' in photo.categories %}
{% if photo.metadata and photo.metadata.exif %}
{{ photo.metadata.exif.DateTimeOriginal }}
{% if photo.metadata.exif.Model %}
{{ photo.metadata.exif.Model }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
```
### Pattern: Documentation with Sibling Pages
```jinja
{{ content|safe }}
{% set related = get_related_posts(currentPath, limit=3) %}
{% if related %}
{% endif %}
```
## Performance Tips
### Caching
Most helper functions are cached automatically. It's safe to call them multiple times:
```jinja
{# 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:
```jinja
✓ 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:
```toml
# config.toml
[server]
debug = true
```
Add debug output to templates:
```jinja
{# Show what get_recent_posts returns #}
{% set posts = get_recent_posts(limit=3) %}
```
## Next Steps
- **[Template Recipes](../recipes/)** - See these helpers in complete working examples
- **[Template System Overview](index.md)** - Understand how templates work
- **[Template Discovery](template-discovery.md)** - Learn how Foldsite finds templates
With these helpers, you can build sophisticated, dynamic sites while keeping your content as simple files and folders!