Magento Layout XML — Layout Handles, Containers and Blocks Guide
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:
defaultcatalog_category_viewcatalog_category_view_type_layeredcatalog_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 movedestination— target container namebefore/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>
Example: Move Related Products to the Bottom
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
nameare merged, not duplicated removeinstructions take effect regardless of ordermoveinstructions 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
Forgetting the XML namespace. Every layout file needs the
xmlns:xsiandxsi:noNamespaceSchemaLocationattributes. Without them, the XML parser throws an error and the layout does not load. Copy the header from an existing working file.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.
Confusing remove with unsetChild.
removedeletes a named element from the entire page.unsetChildremoves a child from a specific parent. Usingremoveinside areferenceContaineris the modern approach. UsingunsetChildwhen you meanremovewill fail silently.Not clearing the correct caches. Layout XML is cached in the
layoutcache and block output is cached inblock_html. If you only clear one cache but not the other, you might see partial or no changes. Always clear both, then flush.Typos in block class names. The
classattribute must be a fully qualified class name.Vendor/Module/Block/Customwill not work — useVendor\Module\Block\Customwith backslashes. A typo here causes a PHP error that breaks the entire page.
Practice Questions
What is the difference between
removeandunsetChild? Answer:removecompletely removes a named element from the page layout regardless of its parent.unsetChildremoves a child element from a specific parent container but the child could still exist elsewhere on the page. Useremoveunless you need fine-grained control within a specific parent.What layout handle would you use to customize the checkout page? Answer: The checkout page uses
checkout_index_index. You would createcheckout_index_index.xmlinVendor/Theme/Magento_Checkout/layout/to add blocks or modify the checkout layout.What does the
htmlTagattribute on a container control? Answer: ThehtmlTagattribute controls which HTML element wraps the container's children. Common values arediv,aside,section,header, andfooter. The container renders as<div class="container-class">children</div>.Challenge: Create a
catalog_product_view.xmlthat moves the product info block (product.info.main) to appear before the product media block (product.info.media), removes theproduct.info.detailsblock, and adds a custom "Ask a Question" button below the add-to-cart section. Use thebefore,remove, andreferenceContainerinstructions.
FAQ
Mini Project
Your task: Customize the Magento Luma theme's product page layout.
- Create a child theme of Luma with
theme.xmlandregistration.php. - Create the directory
app/design/frontend/Vendor/Theme/Magento_Catalog/layout/. - Create
catalog_product_view.xmlthat:- Removes the
product.info.relatedblock - Moves
product.info.detailsbelow the product media - Adds a custom block named
custom.promo.bannerwith a dummy template beforeproduct.info.main - Changes the sidebar layout: remove
catalog.compare.sidebar
- Removes the
- Enable template hints and verify your changes appear on any product page.
- For each change, write down which handle-specific XML file you used and why.
- 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:
- Magento Theme Development — Create your own theme from scratch
- Magento CSS and JavaScript — Style your custom blocks with Less and RequireJS
- PHP OOP Basics — Understand the class structure behind blocks
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro