Facet Template Syntax

Facet Template Syntax

Facet is the template language used by FaceFlow for dynamic rendering.

Use this page as the primary syntax guide. It covers the patterns technical users actually need when writing Component templates, Variable templates, List item output, and other runtime fragments inside FaceFlow.

This page is intentionally detailed. It is not only a quick-start guide. It is the main syntax document for official FaceFlow template authoring.

Syntax Families

Facet templates usually combine five syntax families:

SyntaxRoleTypical Use
{field}Component field outputdirect output from a Component field
{{ expr }}escaped expression outputpage data, context objects, filters
{{{ expr }}}raw outputtrusted rich content and controlled markup
{{#block}}...{{/block}}control flowconditions, loops, page queries, deferred output
[[variable-name]]reusable fragment embeddingshared template fragments with optional attributes

The most common source of confusion is mixing Component field shorthand with general Facet expressions. Keep that boundary clear:

  • use {field} when the owning Component field contract supports direct field output or field helpers
  • use {{ ... }} when rendering general context-aware expressions
  • use [[variable-name]] when the correct solution is reuse, not more template logic

Quick Start

The minimum useful Facet mental model is:

<section class="hero">
  <h1>{{ page.title }}</h1>

  {{#if summary}}
    <p>{{ summary }}</p>
  {{/if}}

  [[sales-contact-badge]]
</section>

That single example already shows the three most important building blocks:

  • value output
  • conditional structure
  • reusable fragment composition

Output Syntax

Component Field Output

In Component templates, direct field output often uses single-brace syntax:

<h2>{title}</h2>
<p>{summary}</p>

Use this when the Component field contract is simple and field-oriented.

For helper-capable fields, the same family extends into helper syntax:

<img src="{heroImage.webp}" alt="{heroImage.description}">
<div data-form-embed="{contactForm}"></div>
<div data-review-embed="{serviceReview}"></div>

Do not assume every field supports helper chaining. Check the owning field reference first.

Escaped Expression Output

Use double braces for normal escaped output:

<h1>{{ page.title }}</h1>
<p>{{ summary | default("No summary available.") }}</p>

This is the default choice for plain text, labels, URLs, dynamic scalar values, and all cases where HTML should be escaped.

Raw Output

Use triple braces when the source is trusted and should render as HTML:

<div class="prose">
  {{{ body }}}
</div>

This is appropriate for:

  • managed rich text
  • controlled HTML fragments
  • trusted editorial content

Do not use raw output for arbitrary untrusted strings.

Comments

Facet supports short and long comments:

{{! short comment }}

{{!--
long comment
spanning multiple lines
--}}

Comments never render into output.

Expression Syntax

Expressions are the foundation of dynamic template output. They let you access nested properties, call helper-style methods, apply filters, and use conditional inline logic.

Dot Path Notation

Access nested properties with dot notation:

{{ page.title }}
{{ page.parent.title }}
{{ page.parent.parent.name }}
{{ user.language.title }}

Arrow notation is also accepted in places where authors naturally type object access:

{{ page->parent->title }}
{{#if page->parent->hasChildren}}
  ...
{{/if}}
{{#each page->children as="child"}}
  ...
{{/each}}

Use whichever style is clearer inside the current template, but stay consistent within one file.

Method-like Calls

Some objects expose method-like transforms or lookups:

{{ page.image.width(800) }}
{{ page.image.size(600, 400) }}
{{ page.image.size(600, 400).webp.url }}

These are common for:

  • images
  • collections
  • page-localized URLs
  • page-aware query helpers

Method-like calls are part of the Facet object model, not arbitrary runtime code execution.

Key-Value Parameters

Some helpers accept named parameters:

{{ page.field name="summary" }}
{{ page.children selector="limit=5" }}
{{ pages.count selector="template=blog-post" }}

Use named parameters when the helper contract explicitly expects them. This keeps templates readable and avoids overloading positional arguments.

Ternary Expressions

Facet supports ternary output logic:

{{ user.isLoggedin ? "Welcome back!" : "Please sign in." }}
{{ page.summary ? page.summary : "No summary provided." }}

Use ternaries for short output decisions. If the branching becomes structural, prefer a block helper such as {{#if}}.

Filter Chains

Use the pipe operator to transform values:

{{ page.title | upper }}
{{ page.body | striptags | truncate(200, "...") }}
{{ published | date("F j, Y") }}
{{ price | currency("$", 2) }}

Filters chain from left to right.

Filter Argument Styles

Facet accepts both parenthesis style and colon style arguments:

{{ page.title | truncate(100, "...") }}
{{ page.title | truncate:100:... }}

Both forms are supported. Prefer parenthesis style in official templates because it is clearer and easier to review.

Block Helpers

Block helpers provide structure, not just scalar output.

Facet supports these core block types:

BlockPurpose
{{#if}}conditional rendering
{{#each}}loop over existing arrays or collections
{{#pages}}query a set of pages
{{#page}}query one page
{{#languages}}iterate available languages
{{#deferred}}cache-compatible deferred rendering

{{#if}}

Use {{#if}} for conditional rendering:

{{#if summary}}
  <p>{{ summary }}</p>
{{/if}}

With else:

{{#if user.isLoggedin}}
  <p>Hello, {{ user.name }}.</p>
{{else}}
  <p>Please sign in.</p>
{{/if}}

With elseif:

{{#if page.template == "blog-post"}}
  <span class="badge">Blog</span>
{{elseif page.template == "case-study"}}
  <span class="badge">Case Study</span>
{{else}}
  <span class="badge">Page</span>
{{/if}}

The else if variant is also accepted:

{{#if page.numChildren > 10}}
  <p>Many children</p>
{{else if page.numChildren > 0}}
  <p>Some children</p>
{{else}}
  <p>No children</p>
{{/if}}

Comparison Operators

OperatorMeaningExample
==equalpage.template == "blog-post"
!=not equalpage.status != 1
>greater thanpage.numChildren > 5
<less thanpage.numChildren < 3
>=greater or equalpage.sort >= 0
<=less or equalpage.sort <= 10

Logical Operators

OperatorMeaningExample
&&ANDuser.isLoggedin && user.isSuperuser
||ORpage.template == "blog-post" || page.template == "news"
!NOT!user.isGuest

Truthiness Rules

Facet truthiness follows a practical template-oriented model:

ValueTruthy?
nullfalsy
falsefalsy
""falsy
0falsy
"0"falsy
empty array or empty collectionfalsy
everything elsetruthy

If the condition logic starts carrying business rules instead of simple display rules, return to the object model. Do not hide weak content design behind nested if blocks.

{{#each}}

Use {{#each}} when the collection already exists in the current context:

{{#each page.children as="child"}}
  <a href="{{ child.url }}">{{ child.title }}</a>
{{/each}}

With shorthand this:

{{#each page.images}}
  <img src="{{ this.url }}" alt="{{ this.description }}">
{{/each}}

With else:

{{#each page.children as="child"}}
  <div>{{ child.title }}</div>
{{else}}
  <p>No child pages found.</p>
{{/each}}

Loop Context Variables

Inside loop blocks, these values are available:

VariableDescription
loop.indexzero-based index
loop.index1one-based index
loop.firsttrue on first iteration
loop.lasttrue on last iteration
loop.lengthtotal item count
loop.eventrue on even indices
loop.oddtrue on odd indices
@indexshorthand alias of loop.index

Example:

{{#each page.children as="child"}}
  <div class="{{#if loop.odd}}row-alt{{/if}}">
    <span>{{ loop.index1 }}.</span>
    {{ child.title }}
  </div>
{{/each}}

this vs named aliases

Inside {{#each}}, this always points to the current item.

{{#each page.children}}
  <a href="{{ this.url }}">{{ this.title }}</a>
{{/each}}

If readability matters, prefer an explicit alias:

{{#each page.children as="child"}}
  <a href="{{ child.url }}">{{ child.title }}</a>
{{/each}}

{{#pages}}

Use {{#pages}} when the template needs to query content rather than render a collection already present in scope.

{{#pages selector="template=blog-post, sort=-created, limit=5" as="post"}}
  <article>
    <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
    <time>{{ post.created | date("F j, Y") }}</time>
    <p>{{ post.summary | truncate(200) }}</p>
  </article>
{{else}}
  <p>No posts found.</p>
{{/pages}}

Typical parameters:

ParameterDescription
selectorquery selector string
asloop variable name

Use {{#pages}} for:

  • archives
  • listing pages
  • recent content widgets
  • navigation collections
  • query-driven content blocks

Use {{#each}} when the collection already exists. Use {{#pages}} when the template must fetch it.

{{#page}}

Use {{#page}} when only one page should be queried:

{{#page path="/about/"}}
  <a href="{{ page.url }}">{{ page.title }}</a>
{{/page}}

Supported lookup patterns include:

{{#page id="1024"}}...{{/page}}
{{#page path="/about/"}}...{{/page}}
{{#page name="contact"}}...{{/page}}
{{#page selector="template=homepage"}}...{{/page}}

Inside a {{#page}} block, page refers to the queried page for the duration of that block.

{{#languages}}

Use {{#languages}} to build language-aware UI such as language switchers:

<nav class="lang-switch">
  {{#languages as="lang"}}
    <a href="{{ lang.url }}" {{#if lang.isCurrent}}class="active"{{/if}}>
      {{ lang.title }}
    </a>
  {{/languages}}
</nav>

With an empty fallback:

{{#languages as="lang"}}
  <a href="{{ lang.url }}">{{ lang.title }}</a>
{{else}}
  {{! single-language site }}
{{/languages}}

Useful language properties include:

  • lang.id
  • lang.name
  • lang.title
  • lang.isDefault
  • lang.isCurrent
  • lang.url
  • lang.httpUrl
  • lang.locale

{{#deferred}}

Use {{#deferred}} when the page is cache-heavy but part of the content depends on per-user or rapidly changing runtime data.

{{#deferred}}
  <span>Welcome, {{ user.name }}!</span>
{{/deferred}}

With custom skeleton sizing:

{{#deferred skeleton-width="12rem" skeleton-height="2em"}}
  <div class="user-panel">
    <span>{{ user.displayName }}</span>
  </div>
{{/deferred}}
{{#deferred skeleton-width="9rem" skeleton-height="1em"}}
{{#if user.isLoggedin}}

<span>Welcome, {{user.name}}!</span>

{{else}}
<a href="/login">Sign In</a>
{{/if}}
{{/deferred}}

Use it for:

  • user-aware greetings
  • account panels
  • cache-safe personalization
  • other content that should not be fixed into a full-page cached HTML response

deferred should be used intentionally. Do not wrap everything dynamic by default.

Context Objects

Facet exposes a set of root-level context objects in every supported render surface.

Common objects include:

  • page
  • pages
  • user
  • languages / lang
  • site
  • now
  • attr
  • loop
  • this

Inside Component templates, Component field values are also exposed at root level:

<h1>{{ title }}</h1>

{{#if showGallery}}
  <div class="gallery">
    {{#each images as="img"}}
      <img src="{{ img.url }}">
    {{/each}}
  </div>
{{/if}}

The deep object reference is documented in:

Image Output Boundary

Image syntax depends on where the image object comes from.

Component image field helper syntax

<img src="{heroImage.width(1200).webp}" alt="{heroImage.description}">

Page or query image object syntax

{{#each page.images as="img"}}
  <img src="{{ img.width(800).webp.url }}" alt="{{ img.description }}">
{{/each}}

Component images field loop syntax

{{#each gallery}}
  <img src="{{this.width(1200).webp}}" alt="{{this.description}}">
{{/each}}

Do not mix these forms casually. The owning field or runtime object decides which syntax is correct.

Map Helper Output

Map-style fields can expose helper-oriented output:

{officeLocation}
{officeLocation.width(600)}
{officeLocation.height(400)}
{officeLocation.size(800,500)}

Use this only when the field contract explicitly documents it.

Variable Embedding

Variables are embedded with double-bracket syntax:

[[site-announcement]]
[[cta-badge label="New" tone="accent"]]

Inside the Variable template, passed values are available through attr.

Variables are for reusable fragments, not for replacing proper object design. If the fragment grows into a section contract, return to Component design.

Managed Embed Markers

Forms and Reviews are typically rendered through managed embed markers.

Fixed model embed:

<div data-form-embed="enterprise-demo-request"></div>
<div data-review-embed="customer-success-reviews"></div>

Field-backed embed inside a reusable Component:

<div data-form-embed="{contactForm}"></div>
<div data-review-embed="{serviceReview}"></div>

Decision rule:

  • fixed embed when the template owns one stable managed workflow
  • field-backed embed when authors should choose the managed model at authoring time

Usage Guidance

  • keep template logic readable
  • prefer strong field contracts over clever template tricks
  • use {{#if}} for structure, not for hiding weak content design
  • use {{#each}} when the collection already exists
  • use {{#pages}} when the template must query the collection
  • use Variables for small reusable fragments
  • return to Component or List design when template logic grows too complex

Common Mistakes

Avoid these patterns:

  • using raw output where escaped output is safer
  • mixing Component field shorthand and generic Facet expressions without understanding the difference
  • solving a content-model problem only with conditionals
  • building one giant nested template instead of clarifying object boundaries
  • repeating the same fragment across templates instead of extracting a Variable
  • using field helper syntax on fields that do not support it

Quick Decision Guide

need to show one value                 -> {field} or {{ expr }}
need trusted HTML output               -> {{{ expr }}}
need to show content conditionally     -> {{#if}}
need to loop existing items            -> {{#each}}
need to query content                  -> {{#pages}} or {{#page}}
need language-aware navigation         -> {{#languages}}
need cache-safe dynamic personalization -> {{#deferred}}
need one reusable fragment             -> [[variable-name]]
need a field-specific helper output    -> {field.helper}
need a full reusable section contract  -> return to Component design

Continue