Guide 04 · Developer docs

Input types, layouts, components and images

The platform's central abstraction, in one line: an input type defines what an editor can enter, a layout area binds one input type to a named slot, a layout groups areas and points at a template, a record or component holds the content for each area, and the template reads it back. Read this book straight after the template languages.

L1–L3
Unit 03 · Layouts

Page and component layouts

The controller field decides what a layout is for: set, it is a design that records of one kind can choose; empty, it is a component that authors place inside other content. Which records see which layouts, which of them use the layout's template, and how a component lists records.

Who this is for. Content modellers deciding whether a design is a page type or a reusable block, and developers wondering why a layout is not offered where they expect it. After this page you can set the controller field on purpose and predict where a layout will appear.

One field makes the difference

A layout with a controller is a page layout: records served by that controller can choose it as their design. A layout whose controller is None (Component) is a component layout: it is never chosen by a record, and is instead offered when an author adds a component to a component or collection area, or creates a global component. Nothing else distinguishes them. The admin's layout list shows component layouts with - (Component) in the controller column.

The controllers on offer

GroupController (as the form lists it)Records that can choose the layout
PagesShow PagePages
BlogsList Blog Posts, Show Blog PostBlogs (their listing), blog posts
BooksShow Book, Show Unit, Show Book PageBooks, units, book pages
FAQShow Page, Show QuestionFAQ pages, FAQ questions
FormsShow FormForms
ProductsList Products, Show Product, Show Product CategoryProduct listings, product descriptions, product category descriptions

When an editor creates or edits a record, the platform offers every layout with that record's controller that is not hidden and belongs to the record's site or to all sites, plus the layout the record already uses, even if that one has since been hidden or moved to another site. On a page, the choice appears only when there is more than one; with exactly one, it is applied without asking. Changing it copies content into the same-named areas of the new layout, as described on Where content is stored.

Which records render through their layout

Pages, blog posts and blog listings, book pages, FAQ pages and questions, product descriptions and custom entity records render through their layout's template. Books and units do not: their controllers always use their default templates (books/books/show.html, books/units/show.html). A book or unit layout still decides the areas an editor fills in; the default template has to read them, and can tell one layout from another by reading the record's layout.

Component layouts

Leave the controller empty and the layout becomes a building block. The convention is to keep component templates under templates/components/; the form then hides the controller field altogether. Which component layouts an area offers is set on the area, through its supported layouts: see Components and collections. A component is data, an instance of its layout with its own content, so one component layout yields any number of components.

Choose a page layout when the design is a whole page that an editor picks once; choose a component layout when the design is a block that authors add, repeat and reorder. A design that is both, such as a hero used at the top of several page types, is a component layout placed in each page layout's component area.

A component that lists records

Widgets, which older sites use to list records such as the latest posts of a blog, are deprecated; a component's template reads the records itself with the query helper $_query, which every template receives (findBy, findOneBy, and findCustomEntityRecordsBy for custom entity records). This component, with the Text areas heading and blogSlug, lists the three latest posts of one blog. It prints the heading with the shorthand and reads blogSlug with getAreaText(), because that value is an argument to the query, not printed:

Template code templates/components/latest-posts.html
{$blog = $_query->findOneBy('Blogs\Entities\Blog', ['slug' => $component->getAreaText('blogSlug')])}
{if $blog}
  {$posts = $_query->findBy('Blogs\Entities\Post', ['blog' => $blog->getId()], ['published' => 'DESC'], 3)}
  <section class="latest-posts">
    <h2>{$component->heading}</h2>
    <ul>
    {foreach $posts as $post}
      <li><a href="{$post->url}">{$post->name}</a></li>
    {/foreach}
    </ul>
  </section>
{/if}
{% set blog = _query.findOneBy('Blogs\\Entities\\Blog', {'slug': component.getAreaText('blogSlug')}) %}
{% if blog %}
  {% set posts = _query.findBy('Blogs\\Entities\\Post', {'blog': blog.id}, {'published': 'DESC'}, 3) %}
  <section class="latest-posts">
    <h2>{{ component.heading }}</h2>
    <ul>
    {% for post in posts %}
      <li><a href="{{ post.url }}">{{ post.name }}</a></li>
    {% endfor %}
    </ul>
  </section>
{% endif %}
Go deeper

Was this page helpful?

Your answer goes to the docs team.