Override A Template File in Magento 2

Published Thursday, January 16, 2020

In this example, I'm going to override the "Magento_Catalog::product/view/addtocart.phtml" template.
The old way of adding a "setTemplate" instruction to layout xml is deprecated, but Magento 2 allows you to pass a "template" argument:

Example: [copy] [hide]

 
  1<referenceBlock name="product.info.addtocart">
  2    <arguments>
  3        <argument name="template" xsi:type="string">Joki_TemplateOverride::product/view/addtocart.phtml</argument>
  4    </arguments>
  5</referenceBlock>
 

But this won't work. Since the "product.info.addtocart" block sets its template with the "template" attribute:

Magento/Catalog/view/frontend/layout/_catalog_product_view.xml [copy]

 
  3<block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart" as="addtocart" template="Magento_Catalog::product/view/addtocart.phtml"/>
 

The "template" attribute takes precedence over any templates passed as arguments.

The best way seems to be through plugins. I'll create a plugin for the block class: "Magento\Catalog\Block\Product\View". In the plugin, we can change the template before it's rendered by adding a "beforeToHtml" method.


File List

  • Joki/TemplateOverride/registration.php
  • Joki/TemplateOverride/etc/module.xml
  • Joki/TemplateOverride/view/frontend/templates/product/view/addtocart.phtml
  • Joki/TemplateOverride/etc/frontend/di.xml
  • Joki/TemplateOverride/Plugin/CatalogBlockProductView.php

registration.php [copy] [view]

 
  1<?php
  2use \Magento\Framework\Component\ComponentRegistrar;
  3
  4ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Joki_TemplateOverride', __DIR__);
 

etc/module.xml [copy] [view]

 
  1<?xml version="1.0"?>
  2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
  3    <module name="Joki_TemplateOverride" />
  4</config>
 

Copy over the `addtocart.phtml` template over to our new module. I changed the button text from "Add to Cart" to "Get It Now!".

view/frontend/templates/product/view/addtocart.phtml [copy] [hide]

  1<?php
  2/** @var $block \Magento\Catalog\Block\Product\View */
  3?>
  4<?php $_product = $block->getProduct(); ?>
  5<?php $buttonTitle = __('Get It Now!'); ?>
  6<?php if ($_product->isSaleable()) :?>
  7<div class="box-tocart">
  8    <div class="fieldset">
  9        <?php if ($block->shouldRenderQuantity()) :?>
 10        <div class="field qty">
 11            <label class="label" for="qty"><span><?= $block->escapeHtml(__('Qty')) ?></span></label>
 12            <div class="control">
 13                <input type="number"
 14                       name="qty"
 15                       id="qty"
 16                       min="0"
 17                       value="<?= $block->getProductDefaultQty() * 1 ?>"
 18                       title="<?= $block->escapeHtmlAttr(__('Qty')) ?>"
 19                       class="input-text qty"
 20                       data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
 21                       />
 22            </div>
 23        </div>
 24        <?php endif; ?>
 25        <div class="actions">
 26            <button type="submit"
 27                    title="<?= $block->escapeHtmlAttr($buttonTitle) ?>"
 28                    class="action primary tocart"
 29                    id="product-addtocart-button" disabled>
 30                <span><?= $block->escapeHtml($buttonTitle) ?></span>
 31            </button>
 32            <?= $block->getChildHtml('', true) ?>
 33        </div>
 34    </div>
 35</div>
 36<?php endif; ?>
 37<script type="text/x-magento-init">
 38    {
 39        "#product_addtocart_form": {
 40            "Magento_Catalog/js/validate-product": {}
 41        }
 42    }
 43</script>

Add the plugin declaration to di.xml.

etc/frontend/di.xml [copy] [hide]

  1<?xml version="1.0"?>
  2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  3    <type name="Magento\Catalog\Block\Product\View">
  4        <plugin name="joki_override_block_catalog_product_view" type="Joki\TemplateOverride\Plugin\CatalogBlockProductView" />
  5    </type>
  6</config>

Add the plugin class. Here is where we check if the template file matches the one we want to override.

Plugin/CatalogBlockProductView.php [copy] [hide]

  1<?php
  2
  3namespace Joki\TemplateOverride\Plugin;
  4
  5class CatalogBlockProductView
  6{
  7    public function beforeToHtml(\Magento\Catalog\Block\Product\View $subject)
  8    {
  9        if($subject->getTemplate() === 'Magento_Catalog::product/view/addtocart.phtml')
 10        {
 11            $subject->setTemplate('Joki_TemplateOverride::product/view/addtocart.phtml');
 12        }
 13    }
 14}

Why do we need to check the template file?

Notice that there are two block declarations with that same class, but they specify different templates.

  • Line 72: "Magento_Catalog::product/view/addtocart.phtml".
  • Line 75: "Magento_Catalog::product/view/options/wrapper.phtml".

Magento/Catalog/view/frontend/layout/catalog_product_view.xml [copy] [hide]

 70<block class="Magento\Catalog\Block\Product\View" name="product.info" template="Magento_Catalog::product/view/form.phtml" after="alert.urls">
 71    <container name="product.info.form.content" as="product_info_form_content">
 72        <block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart" as="addtocart" template="Magento_Catalog::product/view/addtocart.phtml"/>
 73    </container>
 74    <block class="Magento\Framework\View\Element\Template" name="product.info.form.options" as="options_container">
 75        <block class="Magento\Catalog\Block\Product\View" name="product.info.options.wrapper" as="product_options_wrapper" template="Magento_Catalog::product/view/options/wrapper.phtml">
 76            <block class="Magento\Catalog\Block\Product\View\Options" name="product.info.options" as="product_options" template="Magento_Catalog::product/view/options.phtml">
 77                <block class="Magento\Catalog\Block\Product\View\Options\Type\DefaultType" name="product.info.options.default" as="default" template="Magento_Catalog::product/view/options/type/default.phtml"/>
 78                <block class="Magento\Catalog\Block\Product\View\Options\Type\Text" name="product.info.options.text" as="text" template="Magento_Catalog::product/view/options/type/text.phtml"/>
 79                <block class="Magento\Catalog\Block\Product\View\Options\Type\File" name="product.info.options.file" as="file" template="Magento_Catalog::product/view/options/type/file.phtml"/>
 80                <block class="Magento\Catalog\Block\Product\View\Options\Type\Select" name="product.info.options.select" as="select" template="Magento_Catalog::product/view/options/type/select.phtml"/>
 81                <block class="Magento\Catalog\Block\Product\View\Options\Type\Date" name="product.info.options.date" as="date" template="Magento_Catalog::product/view/options/type/date.phtml"/>
 82            </block>
 83            <block class="Magento\Framework\View\Element\Html\Calendar" name="html_calendar" as="html_calendar" template="Magento_Theme::js/calendar.phtml"/>
 84        </block>
 85        <block class="Magento\Catalog\Block\Product\View" name="product.info.options.wrapper.bottom" as="product_options_wrapper_bottom" template="Magento_Catalog::product/view/options/wrapper/bottom.phtml">
 86            <block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart.additional" as="product.info.addtocart" template="Magento_Catalog::product/view/addtocart.phtml"/>
 87        </block>
 88    </block>
 89</block>

Since we don't want to change both templates, we'll need to compare the template in the plugin to make sure it's the one we want to override.

And now the add-to-cart button text is changed.

Updated Friday, January 17, 2020