Handles

A handle names a set of layout instructions and the file that holds them; the handles that apply decide which files load.

Syntax<handle>.xml — e.g. default.xml, catalog_product_view.xml, catalog_product_view_type_configurable.xml

A handle is a name for a set of layout instructions. It is also a file name: a file called catalog_product_view.xml declares instructions for the handle catalog_product_view. Magento works out which handles apply to the request, then loads every file named after each of them from every module and from the theme.

The handles on a storefront page

  • default — applies to almost every page. Site-wide changes go in default.xml.
  • The full action name: route, controller and action joined by underscores and lowercased. The product page is catalog_product_view.
  • Page handles, which append one entity detail to the action name: catalog_product_view_type_simple, catalog_product_view_id_42, catalog_product_view_sku_24-MB01, catalog_category_view_type_layered, catalog_category_view_displaymode_products.
  • Arbitrary handles, which apply to nothing by themselves and are pulled in with <update handle="..."/>. default_head_blocks is one.

Where they come from

The controller's result page adds them. Magento\Catalog\Helper\Product\View calls addPageLayoutHandles(['type' => $product->getTypeId()]), and that call is what produces the _type_simple handle. An administrator can also attach a file-backed handle to one product, named catalog_product_view_selectable_<sku>_<name>.xml; since 2.3.4 the admin field is a picker over those files rather than free text.

What trips people up

The action-name part is lowercase with underscores and must match exactly. A file named catalog-product-view.xml is never loaded and never reports an error. After adding a layout file, run bin/magento cache:flush.

Example

Example · xml
<!-- app/code/Acme/Delivery/view/frontend/layout/catalog_product_view_type_configurable.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.size.guide"
                   template="Acme_Delivery::product/size-guide.phtml"
                   after="product.info.price"/>
        </referenceContainer>
    </body>
</page>

When to use it

  • Show a size guide on configurable products only.
  • Add a banner to every storefront page without touching a core file.
  • Share one set of layout instructions between the cart page and a custom page.
  • Work out the handle name for a controller action you just wrote.

More examples

A handle that applies everywhere

The core Contact module, verbatim. It is the pattern to copy for a site-wide footer link: default.xml, a referenceBlock on footer_links, and ifconfig so the link disappears when the feature is switched off in configuration. Copy it into your own module's default.xml, change the label and path, then run bin/magento cache:flush.

Example · xml
<!-- vendor/magento/module-contact/view/frontend/layout/default.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="footer_links">
            <block class="Magento\Framework\View\Element\Html\Link\Current"
                   ifconfig="contact/contact/enabled" name="contact-us-link">
                <arguments>
                    <argument name="label" xsi:type="string" translate="true">Contact Us</argument>
                    <argument name="path" xsi:type="string">contact</argument>
                </arguments>
            </block>
        </referenceBlock>
    </body>
</page>

One set of instructions, two pages

acme_delivery_promise is an arbitrary handle: no request produces it, so the first file does nothing on its own. The cart page file includes it with <update>, and any other handle can include it the same way. Reach for this when two unrelated pages need identical instructions and you do not want the block declared twice.

Example · xml
<!-- app/code/Acme/Delivery/view/frontend/layout/acme_delivery_promise.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="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="acme.delivery.promise"
                   template="Acme_Delivery::promise.phtml"/>
        </referenceContainer>
    </body>
</page>

<!-- app/code/Acme/Delivery/view/frontend/layout/checkout_cart_index.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="acme_delivery_promise"/>
    <body/>
</page>

A handle tied to one entity

Magento\Catalog\Controller\Category\View calls addPageLayoutHandles(['id' => $category->getId()]), so this file loads only for category 3. It works, and it is usually a trap: the id differs between staging and production, so the file silently does nothing after a database refresh. Prefer the type or displaymode handles, or a configuration flag, unless the entity really is fixed.

Example · xml
<!-- app/code/Acme/Delivery/view/frontend/layout/catalog_category_view_id_3.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="page.main.title" display="false"/>
    </body>
</page>

Discussion

  • Be the first to comment on this lesson.