Single source of truth for Birkly CMS documentation

Birkly fills your HTML with CMS content using a plain-English template language: placeholders like {title} and blocks like {for each entry in 'blog'}…{endfor}. The browser script birkly-client.js (or optional SSR) replaces those tags with live data.

From Guide: The How it works → Site & templates card in admin Guide links here when you are ready to connect project/ HTML to your entries.

In short: Write normal HTML, leave slots where content should come from Birkly, and load birkly-client.js. Collections are quoted ('blog'). System fields are bare ({title}, {slug}); user-defined fields use quotes ({'excerpt'}, {'price'}). List pages loop a collection; detail pages read one entry from the URL — they must not loop the whole collection.

Canonical engine reference: Birkly repo docs/TEMPLATE_LANGUAGE_SPEC.md (Template Language Spec v1.0). Human SSOT pointer: Canonical template syntax.

Templating mental model: HTML + client + CMS = live site

Beginner

Mental model

PieceRole
Your HTMLLayout and template tags in the Light DOM
Collections & entriesContent you edit in admin
birkly-client.jsFetches published data and replaces {…} tags
Public API / page bundleWhere the client loads entries

You do not paste every blog post into HTML. You write a loop once; new published entries appear automatically.

Three page patterns

PatternWhenApproach
ListMany items of one type{for each entry in 'blog' …}{endfor}
DetailOne item from a linkURL ?collection=blog&slug={slug} + {title} / {'content'} — no collection loop
Shared chromeHeader/footer on every page<birkly-region> or {from 'site_elements' get entry '…'}

See Detail pages &amp; URL context and Regions / web components.

Minimal example (blog list)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Blog</title>
</head>
<body>
  <h1>Blog</h1>
  {for each entry in 'blog' with status = published ordered by newest}
    <article>
      <h2><a href="post.html?collection=blog&slug={slug}">{title}</a></h2>
      {if entry has 'excerpt'}
        <p>{'excerpt'}</p>
      {endif}
      <time>{created_at format "F j, Y"}</time>
    </article>
  {endfor}
  <script src="/birkly-client.js"></script>
</body>
</html>

Every templated page needs the client script. On an external site (different domain than the CMS), add data-cms-url="https://your-cms.example". See Client setup. When User, Commerce, or Events plugins are active, storefront behavior loads automatically — Project sites and plugins.

Learning path

  1. Client setup — script tag, same-host vs external, SSR vs client
  2. Project sites and plugins — plugin storefront auto-load, member gates, Birkly.fetch
  3. Variables and output — {title} vs {'field'}
  4. Loops and lists — with, ordered by, limit
  5. Detail pages &amp; URL context — .current, anti-patterns
  6. Conditionals · Dates · From…get
  7. Public forms templating · Worked site walkthrough
  8. Cheat sheet — quick reference + phantoms

Where does replacement happen?

Advanced Users

For developers & AI

Architecture

  1. Template HTML contains Birkly syntax in the Light DOM.
  2. Client discovers regions (syntax-bearing nodes, data-birkly-region, or <birkly-region>).
  3. Page bundle / public API loads published entries (and always prefetches site_elements).
  4. String engine (Birkly.render / processTemplates) replaces tags.
  5. Events: birkly:region-processed, birkly:templates-processed.

Light DOM vs Shadow DOM

SurfaceTemplate languageHow to render
Light DOMYes — {for each}, {from}, regionsWrite tags in markup
Shadow DOMNo auto-scanFetch API + Birkly.render(html, data)

Core rules (must match the engine)

  • Collections always in single quotes: 'blog'.
  • User fields quoted: {'content'}. System: {title}, {slug}, {created_at}.
  • Ordering: ordered by newest / oldest / last-updated / order — not sort="date:desc".
  • Dates: {created_at format "Y-m-d"} — not { date "…" }.
  • Single entry: {from 'blog' get entry 'slug'}…{endget} — not {entry 'blog'.'slug'}…{endentry} (phantom; forms use {entry for} which is different).
  • No Jinja/Liquid: not {{ }}, not {% for %}.

Interlinks for agents

TopicDoc
Field types / schemaField types
Sub-collections modelSub-Collections
FormsPublic forms templating, Public forms
WebMCP / AIWebsite tools, Agent Guide
Connect AI appsSettings → AI — MCP connections
Spec (CMS repo)docs/TEMPLATE_LANGUAGE_SPEC.md