Files
foldsite/docs/content/templates/index.md
Tanishq Dubey ad81d7f3db
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 52s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 1m1s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 5m50s
docs refactor
2025-10-09 18:21:23 -04:00

14 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 System Master Foldsite's powerful cascading template system Learn how Foldsite's hierarchical template discovery works - from simple defaults to sophisticated, context-aware layouts.
base.html is required and wraps every page on your site
Templates cascade from specific to general - Foldsite uses the first match
Use Jinja2 syntax for dynamic content and logic

Template System

Foldsite's template system is both powerful and intuitive. Templates are HTML files with Jinja2 syntax that define how your content is displayed.

The Template Hierarchy

Foldsite uses a cascading template discovery system. When rendering a page, it searches for templates from most specific to most general, using the first match found.

###Understanding Template Priority

Think of it like CSS specificity - more specific selectors override general ones:

Specific file > Type + Extension > Type + Category > Generic type

Example: Rendering content/blog/my-post.md

Foldsite searches in this order:

  1. templates/blog/my-post.html ← Exact file match
  2. templates/blog/__file.md.html ← Markdown files in blog/
  3. templates/blog/__file.document.html ← Document files in blog/
  4. templates/__file.md.html ← All markdown files
  5. templates/__file.document.html ← All document files
  6. templates/__file.html ← Any file
  7. First match wins!

Required Template: base.html

Every Foldsite project MUST have a base.html in the templates root. This wraps every page on your site.

Minimal base.html

<!DOCTYPE html>
<html>
<head>
    <title>My Site</title>
    {% for style in styles %}
    <link rel="stylesheet" href="/styles{{ style }}">
    {% endfor %}
</head>
<body>
    {{ content|safe }}
</body>
</html>

Available Variables in base.html

  • {{ content|safe }} - Rendered page content (required)
  • {{ styles }} - List of CSS files to load
  • {{ currentPath }} - Current page path
  • {{ metadata }} - Frontmatter from markdown files
  • All template helper functions

Complete base.html Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% if metadata and metadata.title %}{{ metadata.title }} - {% endif %}My Site</title>

    {% for style in styles %}
    <link rel="stylesheet" href="/styles{{ style }}">
    {% endfor %}
</head>
<body>
    <header>
        <nav>
            <a href="/">Home</a>
            {% for item in get_navigation_items() %}
            <a href="{{ item.url }}">{{ item.title }}</a>
            {% endfor %}
        </nav>
    </header>

    <main>
        {{ content|safe }}
    </main>

    <footer>
        <p>&copy; 2025 My Site</p>
    </footer>
</body>
</html>

Template Naming Patterns

File Templates

Templates for individual files use this pattern:

Pattern: __file.{extension}.html or __file.{category}.html

Examples:

  • __file.md.html - All markdown files
  • __file.document.html - All document types (md, txt, html)
  • __file.image.html - Individual image pages (rare)
  • __file.jpg.html - Specific to JPG files

Folder Templates

Templates for directory views:

Pattern: __folder.{category}.html

Examples:

  • __folder.md.html - Folders containing mostly markdown
  • __folder.image.html - Photo gallery folders
  • __folder.html - Generic folder view

Specific Page Templates

Override for specific pages:

Pattern: {filename}.html

Examples:

  • index.html - Only for index.md
  • about.html - Only for about.md
  • contact.html - Only for contact.md

File Categories

Foldsite automatically categorizes files by extension:

Category Extensions Template Use Case
document .md, .txt __file.document.html Text content
image .jpg, .png, .gif __file.image.html Photos
multimedia .mp4, .mp3 __file.multimedia.html Video/audio
other Everything else __file.other.html Downloads

Files can have multiple categories. For example, .md files are both md and document.

Template Variables

Every template receives these variables:

Always Available

  • content - Rendered HTML content
  • styles - List of CSS file paths
  • currentPath - Path relative to content root
  • metadata - Frontmatter dict (for markdown files)

Markdown Files Only

For .md files, metadata contains frontmatter:

---
title: "My Blog Post"
date: "2025-01-15"
author: "Your Name"
tags: ["python", "web"]
---

# Content here...

Access in templates:

<h1>{{ metadata.title }}</h1>
<time>{{ metadata.date }}</time>
<p>By {{ metadata.author }}</p>

{% for tag in metadata.tags %}
    <span class="tag">{{ tag }}</span>
{% endfor %}

Template Helpers

Foldsite provides powerful helper functions accessible in all templates:

Content Discovery

{# Get folder contents #}
{% for file in get_folder_contents(currentPath) %}
    <a href="/{{ file.path }}">{{ file.name }}</a>
{% endfor %}

{# Get sibling files #}
{% for sibling in get_sibling_content_files(currentPath) %}
    <a href="/{{ sibling[1] }}">{{ sibling[0] }}</a>
{% endfor %}

{# Get sibling folders #}
{% for folder in get_sibling_content_folders(currentPath) %}
    <a href="/{{ folder[1] }}">{{ folder[0] }}</a>
{% endfor %}

Blog Functions

{# Recent blog posts #}
{% for post in get_recent_posts(limit=5, folder='blog') %}
    <article>
        <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
        <time>{{ post.date }}</time>
    </article>
{% endfor %}

{# Posts by tag #}
{% for post in get_posts_by_tag('python', limit=10) %}
    <a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}

{# All tags #}
{% for tag in get_all_tags() %}
    <a href="/tags/{{ tag.name }}">{{ tag.name }} ({{ tag.count }})</a>
{% endfor %}

Navigation

{# Breadcrumbs #}
{% for crumb in generate_breadcrumbs(currentPath) %}
    {% if not crumb.is_current %}
        <a href="{{ crumb.url }}">{{ crumb.title }}</a>
    {% else %}
        <span>{{ crumb.title }}</span>
    {% endif %}
    {% if not loop.last %} / {% endif %}
{% endfor %}

{# Navigation menu #}
{% for item in get_navigation_items(max_items=10) %}
    <a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}

{# Related posts #}
{% for post in get_related_posts(currentPath, limit=3) %}
    <a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}

See Template Helpers Reference for complete documentation.

Cascading Through Directories

Templates cascade down through your directory structure. Place templates in subdirectories to override for specific sections.

Example Structure

templates/
├── base.html                      # Site-wide wrapper
├── __file.md.html                 # Default for all markdown
├── __folder.image.html            # Default for galleries
├── blog/
│   ├── __file.md.html             # Override for blog posts
│   └── __folder.md.html           # Override for blog index
└── photos/
    └── __folder.image.html        # Override for photo galleries

How It Works

Rendering content/blog/my-post.md:

  1. Looks in templates/blog/ first
  2. Finds blog/__file.md.htmlUses this
  3. Never checks root __file.md.html

Rendering content/projects/project.md:

  1. Looks in templates/projects/ first
  2. Doesn't find specific template
  3. Falls back to templates/__file.md.htmlUses this

Practical Examples

Simple Blog Post Template

templates/__file.md.html:

<article>
    {% if metadata %}
        <header>
            <h1>{{ metadata.title }}</h1>
            <time datetime="{{ metadata.date }}">{{ metadata.date }}</time>
            {% if metadata.author %}
                <p class="author">By {{ metadata.author }}</p>
            {% endif %}
        </header>
    {% endif %}

    <div class="content">
        {{ content|safe }}
    </div>

    {% if metadata and metadata.tags %}
        <footer>
            <p>Tags:
            {% for tag in metadata.tags %}
                <a href="/tags/{{ tag|lower }}">#{{ tag }}</a>
            {% endfor %}
            </p>
        </footer>
    {% endif %}
</article>

Blog Index Template

templates/__folder.md.html:

<div class="blog-index">
    <h1>Recent Posts</h1>

    {% for post in get_recent_posts(limit=10, folder=currentPath) %}
        <article class="post-preview">
            <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
            <time>{{ post.date }}</time>
            {% if post.metadata.description %}
                <p>{{ post.metadata.description }}</p>
            {% endif %}
        </article>
    {% endfor %}
</div>

templates/__folder.image.html:

<div class="gallery">
    {% set breadcrumbs = currentPath.split('/') %}
    <nav class="breadcrumbs">
        <a href="/">Home</a>
        {% for i in range(breadcrumbs|length) %}
            {% if i+1 == breadcrumbs|length %}
                / <span>{{ breadcrumbs[i] }}</span>
            {% else %}
                / <a href="/{{ '/'.join(breadcrumbs[:i+1]) }}">{{ breadcrumbs[i] }}</a>
            {% endif %}
        {% endfor %}
    </nav>

    <div class="photos">
        {% for photo in get_folder_contents(currentPath)|sort(attribute='date_created', reverse=True) %}
            {% if 'image' in photo.categories %}
                <a href="/download/{{ photo.path }}" class="photo">
                    <img src="/download/{{ photo.path }}?max_width=400"
                         alt="{{ photo.name }}"
                         loading="lazy">
                </a>
            {% endif %}
        {% endfor %}
    </div>
</div>

Error Pages

Custom error template: templates/__error.html

<div class="error-page">
    <h1>Error {{ error_code }}</h1>
    <p>{{ error_message }}</p>
    <p>{{ error_description }}</p>
    <a href="/">Return Home</a>
</div>

Variables available:

  • error_code - HTTP status code (404, 500, etc.)
  • error_message - Short message ("Not Found")
  • error_description - Detailed description

Jinja2 Syntax Quick Reference

Variables

{{ variable }}
{{ metadata.title }}
{{ post.url }}

Filters

{{ content|safe }}              {# Don't escape HTML #}
{{ title|upper }}               {# Uppercase #}
{{ date|default('Unknown') }}   {# Default value #}
{{ items|length }}              {# Count items #}

Conditionals

{% if metadata %}
    <h1>{{ metadata.title }}</h1>
{% endif %}

{% if metadata and metadata.title %}
    ...
{% elif metadata %}
    ...
{% else %}
    ...
{% endif %}

Loops

{% for item in items %}
    <p>{{ item }}</p>
{% endfor %}

{% for key, value in metadata.items() %}
    <p>{{ key }}: {{ value }}</p>
{% endfor %}

{# Loop variables #}
{% for item in items %}
    {{ loop.index }}      {# 1-indexed #}
    {{ loop.index0 }}     {# 0-indexed #}
    {{ loop.first }}      {# True on first iteration #}
    {{ loop.last }}       {# True on last iteration #}
{% endfor %}

Filters and Functions

{% for file in get_folder_contents()|sort(attribute='date') %}
    ...
{% endfor %}

{% for post in get_recent_posts(limit=5)|reverse %}
    ...
{% endfor %}

Best Practices

1. Start Simple, Add Complexity as Needed

Begin with basic templates:

templates/
├── base.html
├── __file.md.html
└── __folder.md.html

Add specific overrides only when you need different styling or layout.

2. Keep base.html Minimal

Your base template should handle:

  • HTML document structure
  • CSS loading
  • Site-wide navigation
  • Footer

Leave content-specific layouts to page templates.

3. Use Template Helpers

Don't manually read files or iterate directories. Use helpers:

✓ Good:
{% for post in get_recent_posts(limit=5) %}

✗ Bad:
{# Trying to manually list files - won't work #}

4. Leverage the Cascade

Put general templates at the root, specific ones in subdirectories:

templates/
├── __file.md.html          # Default for all markdown
└── blog/
    └── __file.md.html      # Special layout for blog

5. Test with Debug Mode

Enable debug mode in config.toml to see template discovery:

[server]
debug = true

This shows which templates Foldsite considered and why it chose the one it did.

Common Patterns

Pattern: Site Navigation

In base.html:

<nav>
    <a href="/">Home</a>
    {% for item in get_navigation_items() %}
        <a href="{{ item.url }}"
           {% if currentPath == item.path %}class="active"{% endif %}>
            {{ item.title }}
        </a>
    {% endfor %}
</nav>

Pattern: Sidebar with Recent Posts

<aside>
    <h3>Recent Posts</h3>
    <ul>
        {% for post in get_recent_posts(limit=5) %}
            <li>
                <a href="{{ post.url }}">{{ post.title }}</a>
                <small>{{ post.date }}</small>
            </li>
        {% endfor %}
    </ul>
</aside>

Pattern: Tag Cloud

<div class="tag-cloud">
    {% for tag in get_all_tags() %}
        <a href="/tags/{{ tag.name|lower }}"
           style="font-size: {{ 0.8 + (tag.count * 0.1) }}em;">
            {{ tag.name }}
        </a>
    {% endfor %}
</div>

Next Steps

Master the template system, and you can build any type of site with Foldsite!