Theme Development
Template Syntax

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

  1. Always escape user content - Use {{ }} for user-generated text
  2. Use raw only for trusted HTML - Use {{{ }}} sparingly
  3. Check before accessing - Always verify arrays/objects exist before using
  4. Keep templates clean - Move complex logic to helpers in index.js
  5. Use partials - Break down large templates into reusable components
  6. 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>
{% } %}