Skip to content

Magento Layout XML — Layout Handles, Containers and Blocks Guide

DodaTech Updated 2026-06-27 14 min read

In this tutorial, you'll learn how Magento layout XML controls page structure using layout handles, containers, blocks, and reference instructions to customize any storefront page.

What You'll Learn

  • How page layout handles determine which XML files apply (default, full action names)
  • How to write layout XML with page, body, referenceContainer, and block elements
  • How containers control HTML wrappers with htmlTag, htmlClass, and htmlId
  • How to remove, move, or unset child elements in existing layouts
  • How to add custom blocks with specific templates and arguments

Why It Matters

Layout XML is the backbone of Magento's theming system. Every page on your store — from the home page to the checkout — is assembled by layout XML instructions. Without understanding layout XML, you cannot control where things appear on the page. You will be stuck using CSS hacks to hide elements or editing core templates, which breaks on upgrades. Layout XML gives you surgical control over page structure without touching a single line of PHP.

Real-World Use

A store owner wants to remove the sidebar on their category pages to give products more space. They also want to add a promotional banner above the product list and move the "Recently Viewed" block from the right sidebar to the footer. This is impossible through the admin panel alone. With layout XML, you create a single catalog_category_view.xml file in your theme. You use remove to take out the sidebar, move to relocate the recently viewed block, and referenceContainer to insert the banner in the content area. The whole change takes 15 lines of XML and zero PHP.

Learning Path

flowchart LR
  A["21: Theme Development"] --> B["22: Layout XML
You are here"]:::current B --> C["23: PHTML Templates"] C --> D["24: CSS and JavaScript"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

What Is Layout XML

Layout XML is the configuration language that tells Magento how to assemble a page. Think of it as a blueprint. The blueprint says: "Put the header at the top, the main content in the middle, the footer at the bottom. Inside the main content, put the product list first, then the toolbar." The actual HTML rendering is handled by PHTML template files, which we cover in the next lesson. Layout XML just defines structure.

Every page in Magento has a unique layout handle. A layout handle is an identifier that matches the page's route and action. For example, a category page uses the handle catalog_category_view. The XML files that match this handle are loaded and processed to build the page.

Layout Handles

Layout handles are the keys that connect a URL to a set of layout instructions. Magento generates handles in a specific order of specificity:

Handle Example When It Applies
default default.xml Every page on the storefront
{route_id} catalog.xml All catalog pages
{route_id}_{controller_id} catalog_category.xml All category pages
{route_id}_{controller_id}_{action} catalog_category_view.xml Category view pages only
{route}_{controller}_{action}_id_{id} catalog_category_view_id_5 A specific category ID

The most specific handle wins when there is a conflict. If you want a change on every page, put it in default.xml. If you want a change only on the checkout page, use checkout_index_index.xml.

Full Action Name Handles

The full action name handle — like catalog_category_view or checkout_index_index — is the most commonly used handle for customizations. It follows the pattern {route}_{controller}_{action}. To find the handle for any page, enable template hints in the admin (Stores > Configuration > Advanced > Developer > Debug) and look for the "Layout Handle" line at the top of the page.

Layout File Location

Theme layout files live at:

app/design/frontend/Vendor/Theme/Magento_Catalog/layout/

The Magento_Catalog part is the module name. Each module that contributes to a page has its own layout directory inside the theme. If your theme does not override a layout file, Magento falls back first to the parent theme, then to the module's base layout files in vendor/magento/module-catalog/view/frontend/layout/.

Handle Hierarchy Example

When you visit a category page at /women/tops.html, Magento loads layout handles in this order:

  1. default
  2. catalog_category_view
  3. catalog_category_view_type_layered
  4. catalog_category_view_id_5 (if the category ID is 5)

Each handle's XML file is loaded and merged. Instructions from more specific handles override less specific ones.

XML Structure

A layout XML file follows a strict structure. Here is the skeleton:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="css/custom-styles.css"/>
    </head>
    <body>
        <referenceContainer name="content">
            <block class="Vendor\Module\Block\CustomBlock"
                   name="custom.block"
                   template="Vendor_Module::custom.phtml"/>
        </referenceContainer>
    </body>
</page>

The <page> element is the root. Inside it, you have <head> for CSS and JS assets, and <body> for all structural instructions. The <body> element can contain:

  • <referenceContainer> — modify an existing container
  • <referenceBlock> — modify an existing block
  • <block> — define new blocks
  • <container> — define new containers
  • <move> — move elements
  • <remove> — remove elements

The page Element

The <page> element declares the XML namespace. Without the xmlns:xsi and xsi:noNamespaceSchemaLocation attributes, the XML file will not validate. Every layout XML file needs this exact opening tag.

The body Element

The <body> element contains all the instructions for arranging page components. You can think of <body> as the workspace where you add, remove, and rearrange page elements.

Containers

A container is a wrapper that holds blocks or other containers. It generates an HTML wrapper element around its children. Containers define the structural regions of a page: header, footer, left sidebar, main content, right sidebar.

Container Attributes

<container name="custom.wrapper" htmlTag="div" htmlClass="custom-wrapper" htmlId="main-wrapper" label="Custom Wrapper">
    <block class="Vendor\Module\Block\Example" name="example.block" template="Vendor_Module::example.phtml"/>
</container>
Attribute Purpose Example
name Unique identifier for reference product.info.details
htmlTag HTML tag to wrap children div, aside, section, header
htmlClass CSS class on the wrapper product-info-main
htmlId CSS ID on the wrapper main-content
label Human-readable label (admin) Product Info Main
before Place before named element before="main.content"
after Place after named element after="header"
output Force output even if empty output="1"

Why Containers Matter

Without containers, the page would be a flat list of blocks with no structure. Containers give you the ability to group related blocks and style them together. If you look at a typical Magento page source, you will see <div class="page-wrapper">, then inside it <header>, <main id="maincontent">, <footer>. These are all containers defined in layout XML.

Blocks

A block is a PHP class that generates HTML output. Every visible piece of content on a Magento page comes from a block. Blocks have a class attribute that specifies the PHP class (which extends Magento\Framework\View\Element\Template), a name attribute for unique identification, and a template attribute pointing to the PHTML file.

Block Attributes

<block class="Magento\Catalog\Block\Product\View\Description"
       name="product.info.description"
       template="Magento_Catalog::product/view/attribute.phtml"
       as="description"
       before="-">
    <arguments>
        <argument name="attribute_code" xsi:type="string">description</argument>
    </arguments>
</block>
Attribute Purpose Example
class PHP block class Magento\Catalog\Block\Product\View
name Unique identifier product.info.media
template PHTML template path Magento_Catalog::product/view/media.phtml
as Alias for getChildHtml product.info.media
before Ordering before another block before="product.info.details"
after Ordering after another block after="product.info.media"
cacheable Allow block Caching cacheable="false"

Block Arguments

Block arguments pass data from layout XML into the block. The xsi:type attribute defines the argument's data type:

<arguments>
    <argument name="title" xsi:type="string">Welcome to Our Store</argument>
    <argument name="max_products" xsi:type="number">10</argument>
    <argument name="show_cart_button" xsi:type="boolean">true</argument>
    <argument name="config" xsi:type="object">Vendor\Module\Model\Config</argument>
    <argument name="options" xsi:type="array">
        <item name="sort_by" xsi:type="string">name</item>
        <item name="direction" xsi:type="string">asc</item>
    </argument>
</arguments>

Supported xsi:type values: string, number, boolean, object, null, array, const.

Layout Instructions

Layout instructions are the actions you take inside <body> to modify the page. These are the core tools for customizing layouts.

referenceContainer

Use referenceContainer to modify an existing container. You can add children to it, remove children, or change its attributes.

<referenceContainer name="content">
    <block class="Vendor\Module\Block\PromoBanner"
           name="promo.banner"
           template="Vendor_Module::banner.phtml"/>
</referenceContainer>

The name attribute must match an existing container. Common container names include:

Container Name Location
page.wrapper Outermost wrapper
header.container Header area
content Main content area
sidebar.main Left sidebar
sidebar.additional Right sidebar
footer-container Footer area
page.bottom Bottom of page

referenceBlock

referenceBlock works like referenceContainer but targets existing blocks. Use it to modify block arguments or add children:

<referenceBlock name="product.info.details">
    <arguments>
        <argument name="view_model" xsi:type="object">Vendor\Module\ViewModel\CustomViewModel</argument>
    </arguments>
</referenceBlock>

unsetChild

Remove a specific child element from a container or block by its alias:

<referenceContainer name="sidebar.main">
    <unsetChild name="catalog.compare.sidebar"/>
</referenceContainer>

remove

Remove an entire element by name. This is a top-level action, not nested inside a reference:

<referenceContainer name="sidebar.main">
    <remove name="catalog.compare.sidebar"/>
</referenceContainer>

Wait — actually, <remove> can go directly inside <body> or inside a reference. The newer approach is to use it directly:

<body>
    <remove name="catalog.compare.sidebar"/>
</body>

move

Move an element from one location to another. You specify the element name, the destination container, and optional positioning:

<move element="product.info.details"
      destination="sidebar.main"
      before="-"
      after="catalog.compare.sidebar"/>

Parameters:

  • element — name of the block or container to move
  • destination — target container name
  • before / after — position relative to sibling (use - for first/last)

update

The update instruction includes another layout file's instructions:

<update handle="catalog_category_view_default"/>

This is useful when you want to reuse a set of layout instructions across multiple handles.

UI Components in Layout

UI Components are complex blocks that use Knockout JS for rendering. They are declared in layout XML with a special structure:

<referenceContainer name="content">
    <block class="Magento\Framework\View\Element\Template"
           name="checkout.root"
           template="Magento_Checkout::checkout.phtml"
           cacheable="false">
        <arguments>
            <argument name="jsLayout" xsi:type="array">
                <item name="components" xsi:type="array">
                    <item name="checkout" xsi:type="array">
                        <item name="component" xsi:type="string">Magento_Checkout/js/view/checkout</item>
                        <item name="children" xsi:type="array">
                            <item name="shipping" xsi:type="array">
                                <item name="component" xsi:type="string">Magento_Checkout/js/view/shipping</item>
                            </item>
                        </item>
                    </item>
                </item>
            </argument>
        </arguments>
    </block>
</referenceContainer>

The jsLayout argument defines the JavaScript component tree. This is how Magento's checkout, customer data, and other complex UI components work.

Example: Remove Sidebar on Category Pages

Create the file app/design/frontend/Vendor/Theme/Magento_Catalog/layout/catalog_category_view.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="sidebar.main">
            <remove name="catalog.compare.sidebar"/>
            <remove name="catalog.navigation.renderer"/>
        </referenceContainer>
    </body>
</page>

This removes the compare products sidebar and the layered navigation from all category pages.

Example: Add a Custom Block to the Product Page

Create catalog_product_view.xml:

<?xml version="1.0"?>
<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="Vendor\Module\Block\CustomBlock"
                   name="custom.product.block"
                   template="Vendor_Module::custom.phtml"
                   before="product.info.main"/>
        </referenceContainer>
    </body>
</xml>

Create catalog_product_view.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <move element="product.info.related"
              destination="page.bottom"
              before="-"/>
    </body>
</xml>

Example: Add a CSS Class to the Body

<body>
    <referenceContainer name="page.wrapper">
        <arguments>
            <argument name="htmlClass" xsi:type="string">custom-theme-wrapper</argument>
        </arguments>
    </referenceContainer>
</body>

How Layout Files Merge

When multiple layout handles apply to a page, Magento merges all matching XML files. The merge follows these rules:

  • Instructions from more specific handles override less specific ones
  • Blocks and containers with the same name are merged, not duplicated
  • remove instructions take effect regardless of order
  • move instructions are processed last

This means you can define a block in default.xml and override its template in catalog_category_view.xml:

<!-- default.xml -->
<referenceContainer name="content">
    <block class="Vendor\Module\Block\ProductList" name="product.list" template="Vendor_Module::list.phtml"/>
</referenceContainer>

<!-- catalog_category_view.xml -->
<referenceBlock name="product.list">
    <arguments>
        <argument name="template" xsi:type="string">Vendor_Module::category-list.phtml</argument>
    </arguments>
</referenceBlock>

Debugging Layout XML

Magento provides several tools for debugging layout issues:

Template Hints. Enable from Stores > Configuration > Advanced > Developer > Debug. Set "Template Path Hints" to Yes and "Add Block Class Type to Hints" to Yes. This shows the layout handle, block class, template path, and container name for every element on the page.

Layout XML Logging. You cannot see layout XML directly in Magento's admin, but you can check the generated layout XML by looking in var/page_cache/ or by using a debugging module.

bin/magento dev:template-hints. This CLI command enables template hints from the command line, which is useful when the admin panel itself has layout issues.

Cache clearing is essential when working with layout XML. Always run:

bin/magento cache:clean layout
bin/magento cache:clean block_html
bin/magento cache:flush

Without clearing these caches, your XML changes will not appear on the frontend.

Common Mistakes

  1. Forgetting the XML namespace. Every layout file needs the xmlns:xsi and xsi:noNamespaceSchemaLocation attributes. Without them, the XML parser throws an error and the layout does not load. Copy the header from an existing working file.

  2. Using wrong container name. If you reference a container that does not exist, the block inside simply never renders. No error is shown. The block silently disappears. Check container names against the parent theme's XML files or enable template hints to see real container names.

  3. Confusing remove with unsetChild. remove deletes a named element from the entire page. unsetChild removes a child from a specific parent. Using remove inside a referenceContainer is the modern approach. Using unsetChild when you mean remove will fail silently.

  4. Not clearing the correct caches. Layout XML is cached in the layout cache and block output is cached in block_html. If you only clear one cache but not the other, you might see partial or no changes. Always clear both, then flush.

  5. Typos in block class names. The class attribute must be a fully qualified class name. Vendor/Module/Block/Custom will not work — use Vendor\Module\Block\Custom with backslashes. A typo here causes a PHP error that breaks the entire page.

Practice Questions

  1. What is the difference between remove and unsetChild? Answer: remove completely removes a named element from the page layout regardless of its parent. unsetChild removes a child element from a specific parent container but the child could still exist elsewhere on the page. Use remove unless you need fine-grained control within a specific parent.

  2. What layout handle would you use to customize the checkout page? Answer: The checkout page uses checkout_index_index. You would create checkout_index_index.xml in Vendor/Theme/Magento_Checkout/layout/ to add blocks or modify the checkout layout.

  3. What does the htmlTag attribute on a container control? Answer: The htmlTag attribute controls which HTML element wraps the container's children. Common values are div, aside, section, header, and footer. The container renders as <div class="container-class">children</div>.

  4. Challenge: Create a catalog_product_view.xml that moves the product info block (product.info.main) to appear before the product media block (product.info.media), removes the product.info.details block, and adds a custom "Ask a Question" button below the add-to-cart section. Use the before, remove, and referenceContainer instructions.

FAQ

What is a layout handle in Magento?

A layout handle is a unique identifier for a page in Magento. It follows the pattern {route}_{controller}_{action} — for example, catalog_product_view for the product view page. Magento loads layout XML files that match the current page's handles and merges them to build the page structure. Handles are how Magento knows which layout XML files to apply to which pages.

How do I find the layout handle for a specific page?

Enable template path hints in Stores > Configuration > Advanced > Developer > Debug. Turn on 'Template Path Hints' and 'Add Block Class Type to Hints'. When you reload the storefront page, the layout handle displays at the top of the page. You can also check the page source for the data-layout-handle attribute on the <body> tag.

Where are Magento layout XML files stored?

Layout XML files live in app/design/frontend/Vendor/Theme/Module_Name/layout/ for theme overrides. The same files exist in vendor/magento/module-module_name/view/frontend/layout/ in the module base. You never edit the module files directly — always copy them to your theme first.

Can I use layout XML to remove a block from a specific page only?

Yes. Create a layout file matching the specific page's handle. For example, to remove the sidebar only on the contact page, use contact_index_index.xml. The remove instruction inside that file applies only when that handle is active. The same block remains visible on all other pages.

Why is my layout change not showing on the frontend?

The most common causes are: caching (clear layout and block_html caches), wrong file location (check the module directory matches the block's module), wrong handle name (verify with template hints), or an XML syntax error (check var/log/system.log). Always start debugging by clearing caches and checking the log files.

Mini Project

Your task: Customize the Magento Luma theme's product page layout.

  1. Create a child theme of Luma with theme.xml and registration.php.
  2. Create the directory app/design/frontend/Vendor/Theme/Magento_Catalog/layout/.
  3. Create catalog_product_view.xml that:
    • Removes the product.info.related block
    • Moves product.info.details below the product media
    • Adds a custom block named custom.promo.banner with a dummy template before product.info.main
    • Changes the sidebar layout: remove catalog.compare.sidebar
  4. Enable template hints and verify your changes appear on any product page.
  5. For each change, write down which handle-specific XML file you used and why.
  6. Test that the changes only affect product pages, not category or home pages.

This exercise mirrors real client work. Every Magento developer customizes page layouts daily, and knowing exactly which handle and container to target saves hours of debugging.

What's Next

Now that you control page structure with layout XML, the next step is learning how the actual HTML is rendered:

Continue to Lesson 23: PHTML Templates — Template hints, escaping, and helper methods.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro