What are templates?
Templates let you bind data straight into your HTML — no manual DOM code. Interpolate values with {{ … }}, repeat blocks with ff-each, and show/hide sections with ff-if. Expressions are plain JavaScript and can use anything your JS tab defines, plus the flowfn SDK.
Interpolation
<h1>{{ storeName }}</h1>
<p>{{ items.length }} items · {{ formatPrice(total) }}</p>
- Works in text and in attribute values; output is HTML-escaped automatically, so interpolated data can never inject markup.
- If an expression fails (an undefined variable, a typo), the literal
{{ … }}text stays — your page never blanks out. - Content inside
<pre>and<code>is left alone, so code samples display as written.
Loops
<template ff-each="(row, i) in flowfn.data.get('products')">
<div class="card">
<h3>{{ i + 1 }}. {{ row.name }}</h3>
<p>{{ row.price }}</p>
</div>
</template>
- The
<template>content is cloned once per array element.row in exprworks too if you don't need the index. - Any array works — a data sheet via
flowfn.data.get(…), a variable from your JS, or an inline expression (filter, sort, slice it first if you like). - Loops nest: an inner
ff-eachsees the outer loop's variables.
Conditionals
<template ff-if="flowfn.data.get('products').length === 0">
<p>Nothing here yet — add your first product.</p>
</template>
Re-rendering
- Data sheets re-render automatically — after
flowfn.data.insert / update / delete / fetch, every template refreshes on its own. - Changed one of your own variables? Call
flowfn.render()to refresh.
Raw HTML (escape hatch)
<div ff-html="renderUI()"></div>
ff-html sets the element's innerHTML unescaped from your expression — handy for a function that builds markup. Because nothing is escaped, only feed it markup you constructed yourself; for anything touched by visitor input, stick to {{ … }}.
Good to know
- Templates run in the editor preview and on the published site identically.
- Reusable components compose with templates:
<template ff-each="row in …"><component name="card" title="{{ row.name }}" /></template>passes each row into the component. Inside a loop, write component props with double braces ({{ row.x }}) — single-brace{expr}props evaluate outside the loop and can't seerow. Component bodies may contain their ownff-each/ff-ifblocks. - Props are strings — pass fields (
title="{{ row.name }}"), not whole objects. Inside a component body, bare{{ title }}is a prop slot, while dotted or complex expressions ({{ row.name }},{{ row.price + 1 }}) evaluate against the surrounding loop directly — so a component used insideff-eachcan read the whole row without any props. - Keys/triggers attach after render as usual; give looped elements stable classes if you bind them.