Core dna renders server-side sites with Smarty or with Twig, one engine per site, and treats the two as peers. This guide is the one place both are documented: what they share, each engine's language and the platform's additions and limits, the two side by side, and translation.
L1–L2
Unit 02 · Smarty
What the platform adds to Smarty
SmartyPage 12 of 25Updated
Core dna registers its own tags and modifiers on top of stock Smarty: four translation forms, |format for dates, {json}, {debug_vars}, {error}, and four modifiers. What each one takes, what it prints, the input that makes it fail, and when to reach for it.
Who this is for. The developer on a Smarty site who meets a tag or modifier that is not in the Smarty manual, or wants to know which platform helper fits a job. Afterwards you can use each one correctly and recognise its failure.
The list
Name
Kind
Does
{_}
Function
Translates a key: {_ key='cart.add' domain='site-shop'}
|_
Modifier
Translates a key: {'cart.add'|_:'site-shop'}
{__}
Block
Translates the enclosed text, used as the key
|format
Modifier
Formats a date in the site's date or date-and-time format
{json}
Block
Turns the enclosed text into compact JSON, forgiving a trailing comma before }
{debug_vars}
Function
Shows the values you pass in the debug panel, on development environments only
Four ways to look up the same translation files. The modifier with a domain is the one to reach for: it is the shortest, and naming the domain every time avoids the platform's default domain, admin, which is the admin interface's.
Template codeSmarty only
{'cart.button.add'|_:'site-shop'} {* the usual form *}
{'cart.items'|_:'site-shop':['count' => $itemCount]} {* with parameters *}
{_ key="product.`$code`.label" domain='site-shop'} {* a key built from a variable *}
{__ domain='site-shop'}checkout.terms.full{/__} {* the enclosed text is the key *}
The function needs key=: {_ 'cart.button.add'} does not compile (too many shorthand attributes). The block uses admin when it has no domain=, even after a template has set another default. Files, domains, parameters and the lookup order are in Translating in Smarty.
|format: dates in the site's format
|format formats a date with the format chosen under the site's Internationalization settings: Date format (Globals.i18n.dateFormat, by default d/m/Y) or Date/Time format (Globals.i18n.datetimeFormat, by default d/m/Y H:i). Without an argument it uses the date-and-time format; |format:'date' uses the date format.
Template codeSmarty only
{$page->getPublished()|format:'date'} {* 19/10/2026 with the default setting *}
{$post->getModified()|format} {* 19/10/2026 14:05 *}
{$component->getAreaDate('eventDate')|format:'date'}
{$page->getPublished()|date_format:'j F Y'} {* a format of your own: 19 October 2026 *}
It formats dates only. It takes a date from a record's getter, a date from getAreaDate(), or a Date or Datetime area's content object. An empty value prints nothing. Anything else, such as a number, a price or a date written as text, stops the render with an error. It has no locale: the formats are numeric, so nothing needs translating.
Dates arrive in the site's timezone. Records convert the dates they return, so the value printed is local time.
For numbers and money use |number_format:2 or the number_format() function; for any other date layout, Smarty's |date_format, which takes PHP's date() letters.
A date area read with the shorthand, {$component->eventDate}, already prints in the site's format, and |format cannot take it. Use getAreaDate() when you need to format it.
{json} and |json_encode
Both produce JSON, from opposite directions. |json_encode encodes a value you have built as an array; {json} takes JSON you have written out as text, with template tags inside it, decodes it and encodes it again compactly. Its one concession is a trailing comma before a closing }, which it removes, so a {foreach} can write a comma after every item of an object.
Template codeSmarty only
{* {json}: JSON written out, with a space after each brace so Smarty leaves it alone *}
<script type="application/ld+json">
{json}
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{foreach $page->getAllParents() as $parent}
{ "@type": "ListItem", "position": {$parent@iteration}, "name": {$parent->getName()|json_encode} }{if !$parent@last},{/if}
{/foreach}
],
}
{/json}
</script>
{* |json_encode: the same, built as an array and encoded once *}
{$items = []}
{foreach $page->getAllParents() as $parent}
{$items[] = ['@type' => 'ListItem', 'position' => $parent@iteration, 'name' => $parent->getName()]}
{/foreach}
<script type="application/ld+json">{['@context' => 'https://schema.org', '@type' => 'BreadcrumbList', 'itemListElement' => $items]|json_encode}</script>
Prefer |json_encode. {json} decodes what you write and re-encodes it compactly: JSON it cannot decode, including a trailing comma before ], comes out as the word null; an empty object comes out as []; and every value written into it still needs its own |json_encode, since a quote in a page name has to be escaped there too. Building an array and encoding it once cannot produce invalid JSON.
{debug_vars} and {error}
{debug_vars} takes any named values and shows them, dumped in full, in the debug panel the platform adds at the foot of the page. It works only on a development environment; anywhere else it does nothing and prints nothing, so a forgotten one is harmless. The panel needs the page to have a </body>.
{error} stops the render on purpose. With message= it raises that message; with exception= it raises an exception you hold; with neither, An error occurred rendering the template. A development environment shows the stack trace with your message; production shows the site's error page and logs the message.
Template codeSmarty only
{debug_vars page=$page sections=$page->getAreaCollectionElements('pageSections')}
{if !$component->getAreaImage('image')}
{error message="Hero component `$component->getId()` has no image"}
{/if}
Use {error} for a state that should never reach a visitor and is cheaper to fix than to render around. For content an editor may legitimately leave empty, render nothing instead.
The remaining modifiers
|json_encode: a value handed to JavaScript or written into JSON, such as a job's request body. <script nonce="{$_inlineScriptNonce}">window.siteConfig = {$config|json_encode};</script>.
|intval: a number stored as text, such as a Number or Select area, before arithmetic or a comparison: {if ($component->getAreaText('columns')|intval) > 2}.
|strval: the reverse, a number compared with a string key.
|base64url_decode: a value passed in a URL as URL-safe base64, where - and _ stand for + and / and padding may be missing.
|webix_table: turns a record or a list of records into rows for the admin's Webix data tables. It belongs to admin templates and stops the render for anything that is not a record.
One modifier the policy allows that you should never commit is |dd, which dumps its value and ends the request with a 500 on every environment (The PHP functions allowed as modifiers).