Templates

Templates

A template in Celena is a set of .tpl files inside templates/<slug>/. PHP in .tpl is forbidden (the <?php/<?= tags are escaped as text) — you use only template-engine tags. This is safe, convenient for markup and easy to document.

On first access a .tpl is compiled to PHP and cached in storage/cache/tpl/. Invalidation is automatic, based on the file's modification time.

Syntax

Variables

{title}                <!-- $vars['title'], auto-escaped -->
{user.name}            <!-- dot access -->
{news.body|raw}        <!-- unescaped (trusted HTML) -->

Inline tags

A tag with parameters is a handler call:

{include file="_partials/head.tpl"}
{lang key="hello.world"}
{csrf}                          <!-- hidden field -->
{csrf format="meta"}            <!-- <meta name="csrf-token"> for AJAX -->
{icon name="check" size="20"}   <!-- inline SVG icon -->
{plugin_assets}                 <!-- CSS/JS of active plugins -->

Interpolation {var} is allowed in parameters:

{lang key="greeting" name="{user.name}"}

Conditions

[if {user.role}="admin"]
  Hi, boss.
[else if {user.role}="editor"]
  Hi, editor.
[else]
  Guest.
[/if]

[if {is_published}]            <!-- truthy check -->
  Published
[/if]

Operators: =, !=, >, <, >=, <=. >/< comparisons are numeric, the rest are string. You can compare two variables: [if {a}={b}]…[/if].

Loops

[foreach var="news" as="n"]
  <article>
    <h2><a href="{n.url}">{n.title}</a></h2>
    <small>{n.date} · {n.author}</small>
  </article>
[/foreach]

Helper variables inside a loop:

VariableMeaning
{_index}0-based iteration number
{_total}total elements
{_first}"1" if first, otherwise ""
{_last}"1" if last

If the array is empty, the body isn't rendered. Loops and conditions can be nested.

Comments

{* This text won't appear in the output *}

Theme structure

templates/mytheme/
├── theme.json          theme manifest
├── home.tpl            home page
├── page.tpl            regular page
├── news_list.tpl       news list
├── news_full.tpl       single article
├── search.tpl          search
├── 404.tpl             404 error
├── _partials/          reusable pieces (head, header, footer)
└── assets/             theme CSS/JS

Variables the core always passes

{site.name}   {site.url}
{page.title}  {page.seo_title}  {page.seo_description}  {page.og_image}
{locale}      {canonical_self}
{account.is_guest}  {account.name}

Protecting <script> and <style>

The contents of <script>/<style> are protected from block-tag parsing (CSS selectors like [data-x] won't be mistaken for tags). Inline tags such as {lang …} still work inside scripts — so you can translate JS-generated markup.

Async blocks

A heavy section can be loaded with a separate request after the page renders:

{async block="latest_news" url="/api/blocks/latest_news" placeholder="Loading…"}

Parameters: trigger (load/visible/manual), poll (30s/5m), class. After loading, the container gets the is-loaded class.

Restrictions

  • <?php, <?= — escaped as text.
  • Direct DB/session/file access from a template is not allowed (only via registered tags or async blocks).
  • Third-party CDNs are forbidden (CSP). Keep all assets in templates/<slug>/assets/.