Guide 02 · Developer docs

Template languages: Smarty and Twig

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 03 · Twig

Twig in Core dna

Core dna runs Twig 3.19 with the string extension, autoescaping switched off, strict variables off, no sandbox and a set tag of its own. How the platform configures it, where Twig files live and what they are called, how it differs from the upstream manual, and where errors and debug output show.

Who this is for. The developer starting on a Twig site, whether they know Twig from elsewhere or Core dna from Smarty sites. Afterwards you will know what the platform changes about stock Twig, which page of this unit covers each part, and how to see what went wrong when a template fails.

How the platform configures Twig

Twig is a full peer of Smarty on Core dna: a site set to Twig renders every template with it, and everything in The shared model applies unchanged. What is specific to Twig:

SettingOn Core dnaRead more
VersionTwig 3.19, plus the string extension (twig/string-extra 3.8). No other extra packageWhat the platform adds to Twig
AutoescapingOff. {{ value }} prints the value exactly as it isBelow
Strict variablesOff: an undefined variable or attribute prints nothing. The one strict render is Mailer emailsBelow
SandboxNone: no policy limits what a template may callTwig security
Platform additionsA set tag of its own, the debug_vars function and filter, four coercion filters, its own empty test, json_decodeWhat the platform adds to Twig
The underscore variablesRegistered as Twig globals for a page render, so macros and only includes see themGlobal template variables
TranslationNo trans filter or tag; the translator is the __ variableTranslating in Twig
Compiled templatesKept per site on production, and not rechecked against the file; compiled on every request elsewhereCompilation and caching

The language as you write it here is in Twig syntax: output, attributes and control flow, and composition in Twig inheritance, includes, embed and macros.

Where Twig templates live, and what they are called

Twig templates live in the same templates/ folder as on any site and are found through the same resolution order. Most end in .html, not .twig, which surprises developers from other platforms. The extension means nothing to the engine; it matters only to the admin's layout picker, which offers files ending in .html, .tpl or .twig. A Twig repository typically looks like this:

Shell
templates/
  includes/
    base.html                 the base template every page extends
    elements/anchor-link.html a partial, included by name
    utilities/geo.twig        a macro library, only ever imported
  components/
    hero.html                 a component layout's template
    accordion.html
  pages/pages/
    show.html                 the default page template
    landing.html              a page layout's template
  blogs/posts/
    show.html
  errors/
    not-found.html

A convention many Twig sites follow, and a sensible one: .html for anything a layout points at or a controller renders, .twig for files only ever reached from another template, such as macro libraries. Either works in either role.

Nothing is escaped automatically

Stock Twig escapes every {{ }} by default. Core dna switches autoescaping off, so {{ value }} prints exactly the string it is given, as Smarty's {$value} does. Two consequences:

  • You escape deliberately. A value that came from a visitor, a URL or an imported feed needs |e (short for |escape) written by you. A richtext area holds HTML on purpose and is printed as it is.
  • |raw changes nothing here. It is not wrong, only redundant, and a template full of it suggests the author expected autoescaping.

This is the assumption most likely to follow a Twig developer from another platform, and the usual advice that “Twig is safe by default” does not hold here: neither engine escapes unless asked. Twig security gives the review question to ask about every output.

An undefined variable prints nothing

Templates render with strict variables off. An undefined variable, an attribute a record does not have, and a misspelt method all print nothing, with no error and no log entry. That is why Twig templates lean on |default(…), ?? and is defined, and why a blank value in Twig should make you check the spelling before the content (Response models).

The one render with strict variables on is a Mailer email's subject and body on a Twig site. There an undefined variable makes the render fail, and the platform falls back to plain placeholder substitution, so the mail is sent without the Twig in it taking effect (The one strict render). Strictness is useful there: a merge field that is not defined is a mistake you would rather catch than send as a blank. If a merge field works on the site and not in an email, test it with every variable defined.

How this differs from the upstream manual

  • Autoescaping is off, above.
  • set also stores every value in _templateContext (The platform's set tag).
  • is empty never calls a record empty (The empty test).
  • dump(), trans, {% sandbox %}, {% cache %} and the intl, html and markdown extras are not installed. Each is a compile error that fails the whole template.
  • A site cannot register an extension. A theme has no PHP, and the platform's environment takes no extensions from a site; reusable logic goes in macros (Add a filter or extension to a Twig site).

Where errors show

An unknown filter, function, tag or test is found when the template compiles, so the whole template fails, not only the one expression. An exception thrown while rendering, such as a coercion filter given null, fails the render the same way. Twig's message names the template and the line: Unknown "dump" function in "components/hero.html" at line 12. A development environment shows it on a stack-trace page; production shows the site's errors/fatal-error.html and logs the message.

Debugging

  • See what the controller passed: add ?dev=1 to the URL on a development environment; on production the panel appears only to a signed-in backend user of type developer.
  • Dump a value you choose: {{ debug_vars(page, component) }} or {{ page|debug_vars }} adds it to the same panel on a development environment and prints nothing anywhere else.
  • Test, do not assume: since nothing undefined raises an error, render the page with the content you expect and look for blanks.
Go deeper

Was this page helpful?

Your answer goes to the docs team.