Template Syntax
FeedGen CMS uses a clean, intuitive template syntax for building themes.
Basic Syntax
Output Variables
Use double curly braces for escaped output:
{# Escaped output (safe for user content) #}
<h1>{{ post.title }}</h1>
<p>{{ post.excerpt }}</p>Raw HTML Output
Use triple curly braces for unescaped HTML:
{# Raw HTML (for trusted content) #}
<div class="content">
{{{ post.content }}}
</div>
{# Include partials #}
{{{ include('partials/header') }}}JavaScript Logic
Use curly-percent for logic:
{# Conditionals #}
{% if (post.featuredImage) { %}
<img src="{{ post.featuredImage }}" alt="{{ post.title }}">
{% } %}
{# If-else #}
{% if (posts.length > 0) { %}
<div class="posts">...</div>
{% } else { %}
<p>No posts found.</p>
{% } %}
{# Loops #}
{% posts.forEach(post => { %}
<article>{{ post.title }}</article>
{% }) %}Comments
Comments are removed from output:
{# This comment won't appear in the HTML #}
{#
Multi-line comments
are also supported
#}Control Structures
Conditionals
{# Simple if #}
{% if (user) { %}
<p>Welcome, {{ user.name }}!</p>
{% } %}
{# If-else #}
{% if (post.featuredImage) { %}
<img src="{{ post.featuredImage }}">
{% } else { %}
<div class="placeholder"></div>
{% } %}
{# If-else if-else #}
{% if (post.status === 'published') { %}
<span class="badge-green">Published</span>
{% } else if (post.status === 'draft') { %}
<span class="badge-yellow">Draft</span>
{% } else { %}
<span class="badge-gray">Unknown</span>
{% } %}
{# Ternary operator #}
<div class="{{ post.featured ? 'featured' : '' }}">Loops
{# forEach loop #}
{% posts.forEach(post => { %}
<article class="post">
<h2>{{ post.title }}</h2>
</article>
{% }) %}
{# With index #}
{% posts.forEach((post, index) => { %}
<article class="post post-{{ index }}">
<span>Post #{{ index + 1 }}</span>
<h2>{{ post.title }}</h2>
</article>
{% }) %}
{# Loop with limit #}
{% posts.slice(0, 6).forEach(post => { %}
...
{% }) %}Checking Values
{# Check if exists #}
{% if (post.author) { %}
<span>By {{ post.author.name }}</span>
{% } %}
{# Check array length #}
{% if (categories && categories.length > 0) { %}
<ul>
{% categories.forEach(cat => { %}
<li>{{ cat.name }}</li>
{% }) %}
</ul>
{% } %}
{# Check for empty #}
{% if (!posts || posts.length === 0) { %}
<p>No posts found.</p>
{% } %}Including Partials
Basic Include
{{{ include('partials/header') }}}
{{{ include('partials/footer') }}}Include with Variables
{# Pass data to partial #}
{{{ include('partials/post-card', { post: post }) }}}
{# Multiple variables #}
{{{ include('partials/pagination', {
currentPage: pagination.page,
totalPages: pagination.pages,
baseUrl: '/posts'
}) }}}Nested Includes
Partials can include other partials:
partials/header.html
<header>
{{{ include('partials/logo') }}}
{{{ include('partials/navigation') }}}
</header>Using SDK in Templates
The sdk object is available in all templates:
Content
{# Get latest posts #}
{% const latest = await sdk.content.getLatestPosts(5); %}
{% latest.forEach(post => { %}
<article>{{ post.title }}</article>
{% }) %}Utilities
{# Format date #}
<time>{{ sdk.utils.formatDate(post.createdAt) }}</time>
{# Truncate text #}
<p>{{ sdk.utils.truncate(post.excerpt, 150) }}</p>UI Components
{# Pagination #}
{{{ sdk.ui.pagination({
currentPage: pagination.page,
totalPages: pagination.pages,
baseUrl: '/posts'
}) }}}
{# Breadcrumb #}
{{{ sdk.ui.breadcrumb([
{ label: 'Home', url: '/' },
{ label: 'Posts', url: '/posts' },
{ label: post.title }
]) }}}
{# Menu #}
{{{ sdk.ui.menu('primary') }}}Hooks
{# Allow plugins to inject content #}
{{{ hooks.do('theme_head') }}}
{{{ hooks.do('theme_body_start') }}}
{{{ hooks.do('theme_body_end') }}}
{{{ hooks.do('theme_after_post') }}}Working with Images
Basic Image
{% if (post.featuredImage) { %}
<img src="{{ post.featuredImage }}" alt="{{ post.title }}">
{% } %}Responsive Images
{# Using srcset #}
{% if (post.featuredImageData) { %}
<img
src="{{ sdk.media.getImageUrl(post.featuredImageData, 'md') }}"
srcset="{{ sdk.media.getSrcSet(post.featuredImageData, ['sm', 'md', 'lg']) }}"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="{{ post.title }}"
loading="lazy"
>
{% } %}Picture Element
{{{ sdk.media.getPictureElement(post.featuredImageData, {
sizes: '(max-width: 640px) 100vw, 50vw',
alt: post.title,
className: 'post-image',
loading: 'lazy'
}) }}}Translations
{# Simple translation #}
<span>{{ __('common.readMore') }}</span>
{# With variables #}
<span>{{ __('posts.count', { count: posts.length }) }}</span>Best Practices
- Always escape user content - Use
{{ }}for user-generated text - Use raw only for trusted HTML - Use
{{{ }}}sparingly - Check before accessing - Always verify arrays/objects exist before using
- Keep templates clean - Move complex logic to helpers in
index.js - Use partials - Break down large templates into reusable components
- Add hook points - Allow plugins to extend your theme
Common Patterns
Conditional Classes
<article class="post{% if (post.featured) { %} post--featured{% } %}">Default Values
<title>{{ pageTitle || settings.siteName }}</title>
<meta name="description" content="{{ pageDescription || settings.siteDescription }}">Safe Property Access
{% if (post.author && post.author.name) { %}
<span>By {{ post.author.name }}</span>
{% } %}