How a Page Is Assembled

A Magento page has no single template: XML files from every module and the theme are merged into one tree, then rendered.

Syntaxapp/code/<Vendor>/<Module>/view/<area>/layout/<handle>.xml

A Magento page is not produced by one template. It is described by XML files called layout files, and Magento merges all of them into a single tree of container and block nodes before anything renders. A module is a self-contained folder of code under app/code/Vendor/Module; every enabled module may contribute layout files, and so may the active theme.

What gets merged

For each request Magento works out which layout handles apply. A handle is the name of a set of layout instructions, and also the name of the file that carries them. Magento then collects every file with that name from every module, in the order given by app/etc/config.php, then from the theme and its parents, and merges them in that order. Same-named files add to one another; nothing is replaced.

Three kinds of file

  • view/frontend/layout/<handle>.xml — page configuration, root node <page>. This is where you add blocks. The frontend folder is the area: the storefront, as opposed to adminhtml.
  • view/frontend/page_layout/<name>.xml — the wireframe of containers (1column, 2columns-left), root node <layout>.
  • Generic layout — also <layout>, used for AJAX responses and emails rather than a whole page.

What trips people up

The merged result is cached in the layout cache type, so an edited file can appear to do nothing. Run bin/magento cache:flush after every layout edit. A brand new module also needs bin/magento setup:upgrade before its files are read at all.

Example

Example · xml
<!-- app/code/Acme/Delivery/view/frontend/layout/catalog_product_view.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="product.info.main">
            <block class="Magento\Framework\View\Element\Template"
                   name="acme.delivery.estimate"
                   template="Acme_Delivery::product/estimate.phtml"
                   after="product.price.tier"/>
        </referenceContainer>
    </body>
</page>

When to use it

  • Work out why a block appears on the product page when no file in your module mentions it.
  • Add a delivery-estimate block to the product page without editing a core file.
  • Decide whether a change belongs in a module layout file or in the theme.
  • Find out which containers a page actually has before targeting one.

More examples

The wireframe behind a one-column page

This is the core page layout, unmodified. It holds containers and no blocks: it only creates the named slots. <update handle="empty"/> pulls in empty.xml, which declares root, page.wrapper, main.content, columns and main. Page configuration files then put blocks into those names. Read this file when you need to know whether a container such as sidebar.main exists on the page you are working on.

Example · xml
<!-- vendor/magento/module-theme/view/frontend/page_layout/1column.xml -->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
    <update handle="empty"/>
    <referenceContainer name="page.wrapper">
        <container name="header.container" as="header_container" label="Page Header Container"
                   htmlTag="header" htmlClass="page-header" before="main.content"/>
        <container name="page.top" as="page_top" label="After Page Header" after="header.container"/>
        <container name="footer-container" as="footer" before="before.body.end"
                   label="Page Footer Container" htmlTag="footer" htmlClass="page-footer"/>
    </referenceContainer>
</layout>

Extending the same handle from a theme

The theme file has the same name as the module's file and is merged after it, not instead of it. The layout attribute on <page> switches the product page to the two-column wireframe, which is what makes sidebar.main exist. Layout files are not static assets, so setup:static-content:deploy is not involved; bin/magento cache:flush is enough.

Example · xml
<!-- app/design/frontend/Acme/storefront/Magento_Catalog/layout/catalog_product_view.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      layout="2columns-left"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="product.info.mailto" remove="true"/>
        <referenceContainer name="sidebar.main">
            <block class="Magento\Framework\View\Element\Template"
                   name="acme.returns.policy"
                   template="Acme_Delivery::product/returns.phtml"/>
        </referenceContainer>
    </body>
</page>

Pulling in another handle

<update handle="default_head_blocks"/> includes another handle's instructions recursively, which is how the stylesheets and scripts in the page head reach every storefront page. The same file also drops the formkey block into the content container on every page, which is worth remembering before you remove a core container.

Example · xml
<!-- vendor/magento/module-theme/view/frontend/layout/default.xml (first lines) -->
<page layout="3columns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="default_head_blocks"/>
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\FormKey" name="formkey"/>
        </referenceContainer>
    </body>
</page>

Discussion

  • Be the first to comment on this lesson.