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 01 · The shared model

Which template renders a record

A record is rendered by the template its layout names, not by one tied to its URL or route. Which records follow their layout and which never do, what a controller falls back to, why two pages side by side can render with different templates, and how to find out which template is in play.

Who this is for. The theme developer looking at a page that renders with a template they did not expect, and anyone deciding where a new template has to go. Afterwards you can trace any record to the file that renders it, in either engine.

Record, layout, template

The route decides which controller handles a URL and which record it loads; it does not decide the template. For most content records the controller then asks the record's live revision for its layout, and the layout's Template field names the file:

  1. The visitor opens /developer-docs; the Pages module loads the page with that path.
  2. The page's live revision is on the layout Hub Developer Docs.
  3. That layout's template is pages/pages/hub-devdocs.html, and the controller renders it instead of its default.
  4. The engine finds the file through the resolution order.

The layout belongs to the revision, so an editor who changes a page's layout in a new draft changes nothing on the site until that revision goes live. The engine plays no part in any of this: the same record picks the same file on a Smarty site and on a Twig site.

Which records follow their layout

RecordTemplateVariable it arrives as
PageIts live revision's layout, else pages/pages/show.htmlpage and record
Blog postIts live revision's layout, else blogs/posts/show.htmlpost and record
A blog's post listingThe blog's layout, else blogs/posts/index.htmlblog and record
Book pageIts live revision's layout, else books/pages/show.htmlpage and record
FAQ page, FAQ questionIts live revision's layout, else faq/pages/show.html or faq/questions/show.htmlpage or question, and record
Product descriptionIts live revision's layout, else products/product-descriptions/show.htmlproductDescription and record
Custom entity recordThe layout of its custom entity type, the same for every record of the typeThe type's identifier, such as store, and record
Book, unitAlways books/books/show.html or books/units/show.html; their layout is not used to choose the templatebook or unit, and record
Product category descriptionAlways products/category-descriptions/show.htmlcategoryDescription, with the category's products as products; there is no record

A custom entity type with no layout template, or whose records are not set to have their own URL, answers 404 for its record URLs rather than falling back to anything. Books and units still have layouts and areas, which the template reads; a site that needs two looks for its books tells them apart inside the one template, from the layout's name or a layout area.

When there is no layout

A record without a layout, and every screen that is not a record (the sign-in page, search results, the cart), renders the controller's own template. Unless the controller names one, it is built from the module, the entity and the action, in lower case with hyphens and with no frontend/ segment: pages/pages/show.html, blogs/posts/show.html. The platform ships none of the content ones, so these files are part of every theme (No frontend segment).

Two records of the same type, two templates

Two pages of one site can render with completely different templates, because each carries its own layout. Asking the API for both shows it:

Two pages and the template each one renders with
GET /cdna-api/pages/pages?properties={id,name,fullPath,activeRevision{layout{name,template}}}&|id=72,74 HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response 200 OK
{
  "result": "success",
  "totalCount": 2,
  "data": [
    {
      "id": 72,
      "name": "Developer Documentation",
      "fullPath": "/developer-docs",
      "activeRevision": {
        "layout": {"name": "Hub Developer Docs", "template": "pages/pages/hub-devdocs.html"}
      }
    },
    {
      "id": 74,
      "name": "Changelog",
      "fullPath": "/changelog",
      "activeRevision": {
        "layout": {"name": "Default layout", "template": "pages/pages/show.html"}
      }
    }
  ]
}
Real response from this documentation site

Both are pages, both handled by the same controller, and each renders its own file. A layout is tied to the kind of record it serves (its controller), which is why the layout picker on a page offers page layouts only. Several layouts may name the same template: on this site two different layouts both point at pages/pages/show.html.

When the answer surprises you

Look at the record's layout, not at the controller or the URL. In order:

  1. Read the layout on the live revision. In the admin it is on the record's editing screen; through the API, select activeRevision{layout{name,template}} as above. If an editor has a newer draft on another layout, the site still shows the live one.
  2. Read the layout's Template field. It is a plain path. A typo there, or a file that has moved, sends the request to the end of the resolution chain.
  3. Check the record type in the table above. A book or a unit ignores its layout when the template is chosen; a custom entity record uses its type's layout, not one of its own.
  4. Check which folder supplied the file. A file of the same name in the site's folder wins over the platform's, and the one you edited may not be the one found.
  5. See what the template was given. On a development environment, add ?dev=1 to the page's URL: a panel at the foot of the page lists the variables the controller assigned, with their record types, such as record::Book. On production the panel appears only to a signed-in backend user of type developer.

A signed-in backend user can also open a specific revision with ?revision=<id> on the record's URL. The platform renders that revision with its own layout's template and does not cache the result, which is the quickest way to check a layout change before it goes live.

Go deeper

Was this page helpful?

Your answer goes to the docs team.