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:
| Variable | Meaning |
|---|---|
{_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/.