Main site template with tags
The main template is the frame of every page: header, menu, content area, side column and footer. You write it in plain markup with tags, no PHP.
The main template is the frame of every page: header, menu, content area, side column and footer. Put the file layouts/main.tpl.html in the views/ folder of your theme (themes/<name>/views/layouts/main.tpl.html) and the page is built from it. If the file is missing or broken, the default view is used and the site keeps working. <head>, the engine scripts and the “back to top” button stay with the engine: connect styles and fonts through theme.json and assets.
Ready-made sample: header, menu, column, footer
<header class="site-head"><div class="wrap">
<a class="brand" href="{home}"><span class="brand-name">{site_name}</span></a>
{languages} {search}
[if:user.logged]<a href="{user.url}">{user.name}</a> {logout_button}[else]<a href="{user.login_url}">Sign in</a>[/if]
</div><div class="wrap"><nav class="nav">[each:menu]<a href="{item.url}">{item.title}</a>[/each]</nav></div></header>
<main id="content" class="wrap has-side">
<div>{content}</div>
<aside class="side-col region region--side">{region:sidebar}</aside>
</main>
<footer class="site-foot"><div class="wrap">© {year} {site_name}</div></footer>The classes site-head, wrap, brand, nav, has-side, side-col, site-foot are ready-made classes of the engine’s shared styling: with them the sample immediately looks like part of the site and follows the theme colors.
What you see on a phone (390). The column moves below the content and the search opens under the header. In the sample above, the section menu is not shown on a phone: the menu button is not part of it. There is now a tag for the button, {menu_toggle}: put it in the header instead of the line <nav class="nav">…[each:menu]…</nav> and a "Menu" button appears on the phone.
What the tags output
| Tag | What it outputs | Example |
|---|---|---|
{site_name} | the site name from “Settings → General” | <a href="{home}">{site_name}</a> |
{home} | the home page address | <a href="{home}"> |
{year} | the current year | © {year} |
{locale} | the language code of the page | {locale} |
{canonical} | the main address of this page | <link rel="canonical" href="{canonical}"> |
{theme} | the address of the theme files folder (assets) | <img src="{theme}/img/logo.svg"> |
{content} | the page content (post, shop, forum…) in an engine container: it stays within the screen width and suits the account area, the forum and forms | <main>{content}</main> |
{content_raw} | the same content without the wrapper and without the width limit: only for your own full-page layouts; for the account area, the forum and forms use {content} | <div class="full">{content_raw}</div> |
{languages} | the language switcher (ready-made block) | {languages} |
{search} | the search box (ready-made block) | {search} |
{login} | the “Sign in / Register” block or the name of the signed-in user (ready-made) | {login} |
{poll} | the current poll of the site | {poll} |
{logout_button} | the “Sign out” button with a protection key | [if:user.logged]{logout_button}[/if] |
{menu_toggle} | the “Menu” button and the section menu, which opens with the button on a phone | {menu_toggle} |
{user_menu} | the member menu: profile, messages, panel, sign out | [if:user.logged]{user_menu}[/if] |
{region:header}, {region:before_content}, {region:sidebar}, {region:after_content}, {region:footer}, {region:head_actions} | module blocks placed in this spot of the theme | <aside>{region:sidebar}</aside> |
[each:menu]…[/each] | the items of the site menu; inside, use {item.title}, {item.url}, {item.active} | [each:menu]<a href="{item.url}">{item.title}</a>[/each] |
About the signed-in visitor
| Tag | What it outputs | Example |
|---|---|---|
[if:user.logged]…[else]…[/if] | different text for a guest and a signed-in user | [if:user.logged]Hello![else]Sign in[/if] |
{user.name}, {user.url} | the name and the profile address | <a href="{user.url}">{user.name}</a> |
{user.avatar} | the avatar address; empty if there is no avatar | [if:user.avatar]<img src="{user.avatar}">[/if] |
{user.role} | the role name | {user.role} |
{user.pm_unread} | the number of unread private messages; empty if the module is off | [if:user.pm_unread]Mail: {user.pm_unread}[/if] |
{user.favorites_count} | the number of bookmarks | {user.favorites_count} |
{user.pm_url}, {user.favorites_url} | the addresses of messages and bookmarks | <a href="{user.pm_url}">Messages</a> |
{user.admin_url}, [if:user.can_admin]…[/if] | the panel address and the condition "the user may enter the panel" | [if:user.can_admin]<a href="{user.admin_url}">Panel</a>[/if] |
{user.login_url}, {user.register_url}, {user.forgot_url} | the sign-in, registration and password recovery addresses | <a href="{user.register_url}">Sign up</a> |
Where we are and who came
Only the tags that the file refers to are evaluated: a page without them spends nothing.
| Tag | What it outputs | Example |
|---|---|---|
{page.title}, {page.description} | the title and description of this page (as in <head>) | <h1>{page.title}</h1> |
{page.route}, [if:page.home] | the route name of the page and the condition "this is the home page" | [if:page.home]…[else]…[/if], [if:page.route=news.show]…[/if] |
{page.path} | the page address without the domain | {page.path} |
[if:module.name] | the module is enabled (any name from the modules folder) | [if:module.shop]<a href="/shop">Shop</a>[/if] |
{category.title}, {category.url}, {category.key} | the current category, if a module has named it | [if:category.title]<a href="{category.url}">{category.title}</a>[/if] |
[each:crumbs] {item.title} {item.url} [if:item.last] | breadcrumbs: “Home” plus the module's chain (or the menu item of this page); the last item is marked last | [each:crumbs][ifnot:item.last]<a href="{item.url}">{item.title}</a> › [else]{item.title}[/ifnot][/each] |
{visitor.country} | the visitor's country code (“UA”); taken from a CDN header or the local address database; empty if unknown | [if:visitor.country=UA]…[/if] |
[if:visitor.mobile], [if:visitor.tablet], [if:visitor.desktop], {visitor.device} | the device type, detected from the browser string | [if:visitor.mobile]{menu_toggle}[/if] |
{propose_url} | the “Suggest a post” link; empty if the module or the setting is off | [if:propose_url]<a href="{propose_url}">Suggest a post</a>[/if] |
{social_login} | ready-made “Sign in with …” buttons (only for the networks that are on; the page contains no app keys); empty if there are no networks | <div class="soc">{social_login}</div> |
[if:social.google], {social.google.url}, {social.google.title} | sign-in through a specific network (google, github, yandex, vk): whether it is on, the sign-in start address and the name; you can place the button in your own markup | [if:social.google]<a class="btn-g" href="{social.google.url}">{social.google.title}</a>[/if] |
[if:social.any] | whether at least one network is on | [if:social.any]or[/if] |
{social_profile} | the “Linked services” block for the account area: link and unlink with a password; empty for a guest | {social_profile} |
{user.field:name} | a custom profile field of the signed-in user (the owner creates the field); empty for a guest, for a disabled field and for a field that does not exist; the value is escaped | <p>City: {user.field:city}</p> |
A module names the category and the chain of sections: it puts the key pageContext into the shared view data (View::share) — ['category' => ['title' => '…', 'url' => '/r/…', 'key' => '…'], 'crumbs' => [['title' => '…', 'url' => '/…']]] — or adds them with the filter theme.page_context. Only own addresses are accepted ("/…").
Conditions and loops
[if:name]…[/if] shows the part if the value exists; [ifnot:name]…[/ifnot] shows it if it does not; [if:name=value]…[else]…[/if] compares; [each:list]…[/each] repeats over a list with {item}, {item.key}, {index}. Values from the page data are available by name; each view has its own (see the page “Views on tags”).
Security
PHP, include and function calls do not work in a tag file; the markup is cleaned against an allowlist (no script, style, iframe, event handlers or data-*); addresses may only be http(s)://, /path or #; the engine adds rel="noopener" to links from the template. Values are escaped. Limits: 20000 characters, 400 tags, nesting depth 6, 60 repeats.
Updated 16.10.2026
No one has commented yet. Be the first.