Magento 2 Overriding Native Template File

Overriding a template can be considered as one of the most common tasks for a developer who works on the Magento 2 platform. In the majority of cases, finishing this task is easy peasy. However, in some other cases, it seems like an impossible mission.

Therefore, in today article, I will provide you several methods that you could use in various cases to override a native template file in Magento 2. Besides, I would also love to let you know when you should consider using an alternative to a template override.

Overriding Native Template File

Method 1: Theme file path

The template path method will be used when you want to build a theme.

More specifically, in Magento 2, any module’s or parent theme’s layout, template, or web can be overridden with ease just by placing it in <theme_dir>/<Vendor>_<Module>/path/to/file.

For instance, for the Magento_Theme module, you can place your template in <theme_dir>/Magento_Theme/templates/html/header.phtml, if you wnat the template which is located at <theme_dir>/<Vendor>_<Module>/view/html/header.phtml to be overrided.

In various cases, you will not be able to specify which module the template belongs to as several block definitions in Magento 2 do not have the Vendor_Module prefix. With these cases, the module with the template belongs to will be defined by the block’s class attribute.

Here is an example, if you want to find a block definition in the Magento_Checkout module, your template would be placed inside the Magento_Checkout module directory that is inside your theme.

Method 2: Layout Block Argument

This layout method is suggested using when you want to build a module. In order to use layout XML to override a template, you will only need to override the template argument of the block. Let’s take the template Magento_Wishlist/view/frontend/templates/view.phtml as an example, for the view.phtml to be overridden with your own template, a new layout file have to be created firstly: <Vendor>_<Module>/view/frontend/layout/wishlist_index_index.xml.

Currently, there are two methods which could be applied to override a block argument:

New method

wishlist_index_index.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>
        <referenceBlock name="customer.wishlist">
            <arguments>
                <argument name="template" xsi:type="string">Vendor_Module::view.phtml</argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Old deprecated method

wishlist_index_index.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>
        <referenceBlock name="customer.wishlist">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Vendor_Module::view.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Although it’s said that the new method is the appropriate one to override a template with layout XML, in fact, there are various cases where this method is not working, you can view more in issue #3356. In these situations, the old deprecated method can be used as a temporary solution until someone could resolve the issue.

Next, you must place the new custom template that you have just created in the location where you specified in your layout file. Here, the location is <Vendor>_<Module>/view/frontend/templates/view.phtml.

As the path of the template in a module matches the path which you have set in your template attribute, it does not really matter. In here, I would want to place the template in the same path you found in its original module which starts from the templates directory of the module. But at the same time, I want to add a directory for the template’s module name you are overriding.

For instance, in your module, a wishlist template is put in <Vendor>_<Module>/view/frontend/templates/wishlist/view.phtml. If the template is catalog template it would be put in <Vendor>_<Module>/view/frontend/template/catalog/view.phtml.

However, to make your override takes effect, the is one extra step that you need to complete which is adding a sequence to your module.xml file for the module which contains the layout file you are modifying.

In this example, here is how your etc/module.xml file will look like: module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_Module" setup_version="100.0.0">
        <sequence>
            <module name="Magento_Wishlist"/>
        </sequence>
    </module>
</config>

This step would help make sure that the module Magento_Wishlist be loaded and added to the merged layout file before your module. This is very important as it ensures the layout override of your module will be parsed after the layout XML has been parsed. If not, your layout override will not be applied as it could be referencing something that doesn’t exist yet.

Method 3: Class preference

In reality, there are several block definitions where a template attribute is included, but a name attribute is not. These block definitions cannot be overridden from layout XML using the above methods. However, you can still override the template by utilizing the class preference method if the template attribute of the block does not contain the prefix Vendor_Module.

To help you easier to understand this method, in the following example, you are going to override the block which contains the cart/item/default.phtml template in Magento/Checkout/view/frontend/layout/checkout_cart_item_renderers.xml.

<block class="Magento\Checkout\Block\Cart\Item\Renderer" as="virtual" template="cart/item/default.phtml">

As the scope for the template path is set by the class attribute on the block, a prefix Vendor_Module seems unnecessary if the template and the block’s class are in the same module. In other words, to change the module scope of the template, you only need to replace the class of the block with a class preference in your di.xml file:

di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Checkout\Block\Cart\Item\Renderer" type="Vendor\Module\Block\Checkout\Cart\Item\Renderer" />
</config>

Although you don’t actually need to do any customization with the block, you still have to ensure that the block actually exists. In order to ensure this, you just need to create a skeleton block.

Renderer.php

<?php
/**
 * This block serves as a skeleton class to change the scope of a block definition. 
 * The template attribute on the block will now default to this module rather than the 
 * core module on the original block definition.
 */
namespace Vendor\Module\Block\Checkout\Cart\Item;
class Renderer extends \Magento\Checkout\Block\Cart\Item\Renderer {}

Then, to finish overriding the template, you only need to add your template just like when you do a normal template override from the layout. In this example, the path is Vendor/Module/view/frontend/templates/cart/item/default.phtml.

You need to be obvious that this solution is only prudent in a small number of cases.

Method 4: Plugin

The class preference could be an acceptable way if you want to override the template for all instances of a class and when the prefix Vendor_Namespace is missing from the template attribute on block definition. However, in specific cases, you need to be more targeted. This is where a plugin comes in handy.

In the example below, you are going to use a plugin to override Magento_Catalog::category/products.phtml with your own template.

Here is how the original block definition for the category view template looks like:

<block class="Magento\Catalog\Block\Category\View" name="category.products" template="Magento_Catalog::category/products.phtml">

The plugin will have to hook into the class Magento\Catalog\Block\Category\View. The template will be retrieved and turned into html using the toHtml() method. To override that template, you need to change the $_template variable to your own template. Luckily, a method to do that has already been created in the Template class. With this understanding, your plugin and di.xml files can be created.

di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Block\Category\View">
        <plugin name="module_catalog_category_view_override_template" type="Vendor\Module\Plugin\Catalog\Block\Category\View" />
    </type>
</config>

View.php

<?php
namespace Vendor\Module\Plugin\Catalog\Block\Category;

class View
{
    public function beforeToHtml(\Magento\Catalog\Block\Category\View $subject)
    {
        if ($template === 'Magento_Catalog::category/products.phtml') {
            $subject->setTemplate('Vendor_>Module::catalog/category/products.phtml');
        }
    }
}

Finally, to finish the template override process, you just need to place your template in the appropriate location which is <Vendor>/<Module>/view/frontend/templates/catalog/category/products.phtml in the module.

Remember that you should only use this method if the block does not have any name and if it has a Vendor_Module prefix and/or has a class which could handle multiple templates.

Alternative methods

Overriding a template is not always a good option for you when modifying something on a page. In the various occasion, it would be better for you if you choose an alternative.

The layout structure

Various times, the only change which is required to a template is an addition to the template’s beginning or end. In such cases, if the block which defines the template you want to override is placed inside a container, you just need to create your own template file and then locate it before or after the template that you want to override. The characteristic which you could use to distinguish blocks and containers is that the blocks’ children have to be explicitly called to be rendered while the containers’ children render all of their children blocks and containers. So your new markup is required to be added to the wishlist page, which would create your own layout/wishlist_index_index.xml file, just like this:

<?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="Magento\Framework\View\Element\Template" name="some.block" template="Vendor_Module::some-template.phtml" after="customer.wishlist"/>
        </referenceContainer>
    </body>
</page>

Remove an element with JS or CSS

Although this method is not recommended, in some cases where you want to avoid remove an element by overriding a template, you can remove that element using JS or CSS. But you have to be careful as if you use this too frequently, large chunks of HTML will be loaded without being displayed to the user. As a result, page load times will be increased which lead to messy and hard to maintain HTML and CSS. So only use this method when you badly need it.

Replace a jQuery widget

In various situation, overriding the template where JS gets initialized is not required when you want to override JS in Magento 2. Instead, Functions and objects on a jQuery UI widget can be overridden much like when a variable or method on a PHP class which you are extending is overridden

In order to do this, your JS file firstly needs to be created in your theme or module:

  • In your theme: <Vendor>/<Theme>/web/js/customAccordion.js
  • In your module: <Vendor>/<Module>/view/frontend/web/js/customAccordion.js

customAccordion.js

define([
  'jquery',
  'jquery/ui',
  'mage/accordion'
], function($){
  $.widget('Vendor_Module.customAccordion', $.mage.accordion, {
      _create: function() {
          // custom code here
      }
  });

  return $.Vendor_Module.customAccordion;
});

Next, let’s create a mapping for your customAccordion widget:

  • In your theme: <Vendor>/<Theme>/requirejs-config.js
  • In your module: <Vendor>/<Module>/view/frontend/requirejs-config.js

requirejs-config.js

var config = {
    map: {
        '*': {
            'accordion': 'Vendor_Module/js/customAccordion'
        }
    }
};

After finishing the above steps, your custom accordion will be loaded in any places where you include or initailize accordion.

Change a line of text by adding a translation

In Magento 2, there is a super simple method to override strings of text. The method is adding a translation to your theme or module which matches the text line you want to change and then replaces it with any string you set. This approach is not the best practice one but in various situation, it is worth the trade-off. This is because, in such cases, instead of having to override a huge number of template, you only need to make a simple text change. However, be careful with it if the string that you are translating is used in different locations.

Conclusion

Template overrides are considered as a quick and easy way to make changes in Magento 2, therefore they can be easily abused. Above, I have just shown you the upsides and downsides of Magento 2 template overrides. Hope it could be helpful for you when choosing an appropriate method to use. If you have any questions or new ideas, feel free to leave a comment below.

Enjoyed the tutorial? Spread it to your friends!