Facet: A Template Engine For FaceFlow

Facet is the rendering engine behind FaceFlow. It lets designers and front-end developers write dynamic templates using a familiar Handlebars / Mustache syntax — directly inside a visual editor, with zero server-side coding required.

Facet: A Template Engine For FaceFlow

Facet Template Engine

A sandboxed, Handlebars-style template engine built for visual page builders and SaaS platforms.


What Is Facet?

Facet is the rendering engine behind FaceFlow. It lets designers and front-end developers write dynamic templates using a familiar Handlebars / Mustache syntax — directly inside a visual editor, with zero server-side coding required.

Unlike traditional CMS templates (which are server-side code files that can execute arbitrary logic), Facet parses every template into an Abstract Syntax Tree (AST) before execution. No eval(), no code injection, no surprises.

The Facet syntax is the universal template engine of FaceFlow, so it can be used in any HTML editing area of FaceFlow, such as variable, layout, component, list-page, and others.


When to Use Facet

ScenarioWhy Facet Fits
SaaS / Multi-tenantUsers edit templates in the browser. Facet's sandbox guarantees one tenant can never affect another.
Visual page buildersDesigners drag-and-drop components — Facet renders them without touching server code.
Headless CMS front-endsPull page data, user state, and site settings into clean templates with dot-path expressions.
Cache-critical sitesBuilt-in {{#deferred}} block lets you serve fully cached pages while still personalizing per-user content via AJAX.
Teams with mixed skill levelsFront-end developers and designers can build templates without learning a server-side language.

Syntax at a Glance

Facet follows the Handlebars / Mustache convention: double curly braces for output, # for block openers, / for closers.

Output

{{ page.title }}                          {{! HTML-escaped output }}
{{{ page.body }}}                        {{! Raw / unescaped output }}
{{ page.title | upper }}                  {{! Filter chain }}
{{ user.isLoggedin ? "Hi!" : "Guest" }}   {{! Ternary }}

Blocks

{{#if page.hasChildren}}                  {{! Conditional }}
  ...
{{/if}}

{{#each page.images as="img"}}            {{! Loop }}
  <img src="{{ img.url }}">
{{/each}}

{{#pages selector="template=blog, limit=5" as="post"}}   {{! Query }}
  <a href="{{ post.url }}">{{ post.title }}</a>
{{/pages}}

Comments

{{! stripped from output }}
{{!-- multi-line
      comment --}}

Variables (Reusable Fragments)

[[pricing-card plan="Pro" price="29"]]

That's the entire surface area. If you know Handlebars, you already know Facet.


Key Differences from Handlebars / Mustache

While Facet borrows the {{ }} / {{# }} syntax, it is not a Handlebars or Mustache implementation. Here are the intentional differences:

FeatureHandlebars / MustacheFacet
Execution modelJavaScript runtimeServer-side AST evaluator (no eval())
Filter syntaxCustom helpersPipe \| chains: {{ val \| upper \| truncate(50) }}
Data queriesExternal (fetch/API)Built-in {{#pages}} / {{#page}} with selector syntax
Image transformsNot built-in{{ img.size(600, 400) }}, .webp, .width(), .height()
Loop metadata@index, @first, @lastloop.index, loop.first, loop.last, loop.even, loop.odd, loop.length + @index sugar
Deferred renderingNot available{{#deferred}} for full-page cache compatibility
Security sandboxPartial (no built-in policy)Full: root whitelist, filter whitelist, field blocking, selector sanitization, loop limits
Ternary expressions{{#if}} block onlyInline {{ cond ? "a" : "b" }}
Raw output{{{ }}}{{{ }}} (same), policy-controlled
Partials{{> partial}}[[variable-name attr="value"]] with attributes, CSS/JS isolation, and cache modes

Built-in Context Objects

Every template automatically has access to:

ObjectDescription
pageCurrent page — title, URL, fields, images, children, parent, etc.
pagesQuery API — find pages by selector from anywhere in the site
userCurrent user — name, login state, roles
languages / langLanguage list — build multilingual switchers
siteSite info — name, URL, settings
nowCurrent date/time — now.year, now.date, now.datetime
attrAttributes passed to a variable/component
loopLoop metadata — index, first, last, even, odd, length

43 Built-in Filters

Filters transform output with the pipe operator. A few highlights:

{{ title | upper }}                      {{! "HELLO WORLD" }}
{{ title | truncate(50) }}              {{! "Hello Wor..." }}
{{ price | currency("€", 2) }}          {{! "€29.00" }}
{{ page.created | date("M d, Y") }}     {{! "Mar 06, 2026" }}
{{ page.modified | relative }}           {{! "3 hours ago" }}
{{ tags | pluck("title") | join(", ") }} {{! "News, Tech, Design" }}
{{ page.image | size(600, 400) }}        {{! Resized image URL }}
{{ data | json }}                         {{! XSS-safe JSON }}
{{ text | slug }}                         {{! "my-page-title" }}

Full list: escape / e, raw, upper, lower, capitalize, trim, truncate, nl2br, striptags, replace, split, slice, pad, wrap, slug, default, number, currency, abs, round, ceil, floor, date, datetime, relative, url_encode, url, json, join, first, last, count, length, reverse, sort, pluck, unique, batch, keys, values, merge, size, md5.


Security Model

LayerWhat It Does
No eval()Templates are lexed → parsed → evaluated as an AST tree. No dynamic code execution.
Root whitelistOnly approved objects (page, user, site, …) can be accessed.
Filter whitelistOnly registered filters can be called.
Block whitelistOnly approved block types (if, each, pages, …).
Field blockingSensitive fields (password, roles, permissions, …) always return null.
Selector sanitizationDangerous patterns (include=, check_access=0) are stripped; limits enforced.
Loop limitsMax depth: 3 nested loops. Max items: 500 total per render.
Raw output control{{{ }}} requires explicit policy opt-in.

Cache-Compatible by Design

Facet integrates with full-page caching out of the box via {{#deferred}}:

{{#deferred skeleton-width="8rem" skeleton-height="1.5em"}}
  Welcome, {{ user.name }}!
{{/deferred}}
  1. Server outputs an animated skeleton placeholder.
  2. Template body is stored in server-side cache.
  3. After page load, a single AJAX call resolves all deferred blocks at once.
  4. DOM is hydrated with personalized content.

Result: fully cached pages with per-user dynamic content — no compromises.


Quick Comparison

FeatureFacetTwigBladeHandlebars.jsMustache
Sandbox safety✅ Full⚠️ Config needed❌ Raw code
Server-side❌ Client-side❌ Client-side
Data query built-in{{#pages}}
Image transforms.size() .webp
Full-page cache compat{{#deferred}}
Visual editor integration
Filter pipe syntax\| filter\| filter
Learning curveLowMediumMediumLowVery Low

Learn More

  • Full Syntax Reference → — Complete developer guide with all 6 block types, 43 filters, context objects, cookbook recipes, and FAQ.

Facet Template Engine · Part of the FaceFlow platform

kingleoric

kingleoric

Discussion

Join the conversation and share your thoughts

No comments yet. Be the first to share your thoughts!