# Create a Custom Template URL: /docs/theming/templating/kida/create-custom-template/ Section: kida Tags: how-to, templates, kida -------------------------------------------------------------------------------- Create a Custom Template Learn how to create a custom template using Kida syntax. This guide walks through building a blog post template from scratch. Goal Create a custom blog post template that: Extends the base layout Displays post metadata (title, date, author) Shows tags and categories Includes a reading time estimate Prerequisites Bengal site initialized Kida enabled in bengal.yaml: site: template_engine: kida Steps Step 1: Create Template Directory Create the template file in your project: mkdir -p templates/blog touch templates/blog/single.html Bengal searches templates in this order: templates/ (your project's custom templates) Theme templates (for each theme in the theme chain): Site-level themes (themes/{theme}/templates) Installed themes (via package entry points) Bundled themes (bengal/themes/{theme}/templates) Default theme templates (ultimate fallback) Step 2: Extend Base Layout Start by extending the base layout: {# templates/blog/single.html #} {% extends "baseof.html" %} Step 3: Define Template Variables Use {% let %} for template-wide variables: {% extends "baseof.html" %} {% let post = page %} {% let reading_time = post.content | reading_time %} {% let author = site.authors[post.author] ?? {} %} Step 4: Override Content Block Override the content block to display your post: {% extends "baseof.html" %} {% let post = page %} {% let reading_time = post.content | reading_time %} {% let author = site.authors[post.author] ?? {} %} {% block content %} <article class="blog-post"> <header> <h1>{{ post.title }}</h1> <div class="post-meta"> <time datetime="{{ post.date | dateformat('%Y-%m-%d') }}"> {{ post.date | dateformat('%B %d, %Y') }} </time> {% if author.name %} <span class="author">By {{ author.name }}</span> {% end %} <span class="reading-time">{{ reading_time }} min read</span> </div> </header> <div class="post-content"> {{ post.content | safe }} </div> {% if post.tags %} <footer class="post-footer"> <div class="tags"> {% for tag in post.tags %} <a href="{{ tag_url(tag) }}" class="tag">{{ tag }}</a> {% end %} </div> </footer> {% end %} </article> {% end %} Step 5: Add Pattern Matching for Post Types Use pattern matching to handle different post types: {% block content %} {% match post.type %} {% case "blog" %} <article class="blog-post"> {# Blog post template #} <h1>{{ post.title }}</h1> {{ post.content | safe }} </article> {% case "tutorial" %} <article class="tutorial"> {# Tutorial template #} <h1>{{ post.title }}</h1> <div class="tutorial-content">{{ post.content | safe }}</div> </article> {% case _ %} <article> {# Default template #} <h1>{{ post.title }}</h1> {{ post.content | safe }} </article> {% end %} {% end %} Step 6: Use Pipeline Operator for Data Processing Process collections with the pipeline operator: {% let recent_posts = site.pages |> where('type', 'blog') |> where('draft', false) |> sort_by('date', reverse=true) |> take(5) %} {% block content %} <article class="blog-post"> {# Post content #} </article> {% if recent_posts %} <aside class="related-posts"> <h2>Related Posts</h2> <ul> {% for related in recent_posts %} <li><a href="{{ related.url }}">{{ related.title }}</a></li> {% end %} </ul> </aside> {% end %} {% end %} Step 7: Add Fragment Caching Cache expensive operations: {% block content %} <article class="blog-post"> {# Post content #} </article> {% cache "related-posts-" ~ post.id %} {% let related = site.pages |> where('type', 'blog') |> where('tags', post.tags[0]) |> where('id', '!=', post.id) |> take(3) %} {% if related %} <aside class="related-posts"> <h2>Related Posts</h2> <ul> {% for item in related %} <li><a href="{{ item.url }}">{{ item.title }}</a></li> {% end %} </ul> </aside> {% end %} {% end %} {% end %} Complete Example Here's a complete blog post template: {# templates/blog/single.html #} {% extends "baseof.html" %} {% let post = page %} {% let reading_time = post.content | reading_time %} {% let author = site.authors[post.author] ?? {} %} {% let related_posts = site.pages |> where('type', 'blog') |> where('tags', post.tags[0] ?? '') |> where('id', '!=', post.id) |> sort_by('date', reverse=true) |> take(3) %} {% block content %} <article class="blog-post"> <header> <h1>{{ post.title }}</h1> <div class="post-meta"> <time datetime="{{ post.date | dateformat('%Y-%m-%d') }}"> {{ post.date | dateformat('%B %d, %Y') }} </time> {% if author.name %} <span class="author">By {{ author.name }}</span> {% end %} <span class="reading-time">{{ reading_time }} min read</span> </div> </header> <div class="post-content"> {{ post.content | safe }} </div> {% if post.tags %} <footer class="post-footer"> <div class="tags"> {% for tag in post.tags %} <a href="{{ tag_url(tag) }}" class="tag">{{ tag }}</a> {% end %} </div> </footer> {% end %} </article> {% cache "related-posts-" ~ post.id %} {% if related_posts %} <aside class="related-posts"> <h2>Related Posts</h2> <ul> {% for related in related_posts %} <li> <a href="{{ related.url }}">{{ related.title }}</a> <span>{{ related.date | days_ago }} days ago</span> </li> {% end %} </ul> </aside> {% end %} {% end %} {% end %} Testing Test your template: # Build the site bengal build # Or run dev server bengal serve Visit a blog post page to see your custom template in action. Next Steps Add Custom Filters — Extend Kida with your own filters Use Pattern Matching — Clean up conditional logic Cache Fragments — Improve performance Info Seealso Kida Syntax Reference — Complete syntax documentation Template Functions — Available filters and functions -------------------------------------------------------------------------------- Metadata: - Author: lbliii - Word Count: 804 - Reading Time: 4 minutes