Add custom layout handle to product page – Magento 2

Have you ever struggled with how to programmatically add a custom layout handle to a product page and what’s the best way to do it? Use the power of Magento 2 plugins (also called interceptors) added into the right place and you’re done!

Disclaimer:

This post assumes the reader is familiar with Magento 2 modules and how they work. If not, it is highly recommended to read the documentation first. All files mentioned below must be placed inside a module.

In a previous article we learned how to add programmatically a custom layout to category page in Magento 2 and we did that using events and observers, as the preferred way – using plugins – was not in place.

Fortunately, to add a new custom layout handle to a product page programmatically/dynamically, we won’t use observers but a plugin instead. As mentioned in that article, we should avoid using observers. And that’s not cause we want so – it’s stated in Magento’s Technical Guidelines documentation.

You may want to add a custom handle for many reasons, such as: change some blocks positions on the page, have custom design for some kind of products (with specific attribute), change page view from 1-column to 2-columns-left, you name it.

No matter what your reason is, you’ll end up having a plugin for  Magento\Catalog\Helper\Product\View:initProductLayout(), like this:

<!-- file: [Vendor]\[Namespace]\Plugin\Catalog\Product\AddCustomLayoutHandleToProductPlugin.php -->
<?php
/**
 * @author    Atwix Team
 * @copyright Copyright (c) 2018 Atwix (https://www.atwix.com/)
 */

namespace [Vendor]\[Namespace]\Plugin\Catalog\Product;

use Magento\Catalog\Helper\Product\View as ProductViewHelper;
use Magento\Catalog\Model\Product;
use Magento\Framework\DataObject;
use Magento\Framework\View\Result\Page as ResultPage;
use Magento\Framework\View\Result\Page;

/**
 *  AddCustomLayoutHandleToProductPlugin
 */
class AddCustomLayoutHandleToProductPlugin
{
    const PRODUCT_LAYOUT_HANDLE = 'product_page_with_custom_handle';

    /**
     * @param  ProductViewHelper $subject
     * @param  Page            $resultPage
     * @param  Product         $product
     * @param  null|DataObject $params
     * @return array
     */
    public function beforeInitProductLayout(
        ProductViewHelper $subject,
        $resultPage,
        $product,
        $params
    ) {
        if ($resultPage instanceof ResultPage && /* your additional checks here */) {
            $resultPage->addHandle([static::PRODUCT_LAYOUT_HANDLE]);
        }

        return [
            $resultPage,
            $product,
            $params
        ];
    }
}

Also, to make it work, you’ll need to include the following entry in your module’s di.xml:

<!-- file: [Vendor]/[Namespace]/etc/frontend/di.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!--
/**
 * @author Atwix Team
 * @copyright Copyright (c) 2018 Atwix (https://www.atwix.com/)
 */-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Helper\Product\View">
        <plugin name="add_custom_layout_handle_to_product_page"
                type="[Vendor]\[Namespace]\Plugin\Catalog\Product\AddCustomLayoutHandleToProductPlugin"/>
    </type>
</config>

Please note that the path for the di.xml file should be as following: [Vendor]/[Namespace]/etc/frontend/di.xml since we want to add custom layout only to product page on storefront.

It is always a good idea to place modifications you want into the correct area, so you don’t bother the part of the system that shouldn’t know about/listen to your changes. Keep it simple.

You may ask why we added the plugin for Magento\Catalog\Helper\Product\View:initProductLayout() and not for Magento\Catalog\Controller\Product\View:execute().

In the controller, the result page is getting prepared only at the end of the method (lines 109-120):

$this->viewHelper->prepareAndRender($page, $productId, $this, $params);

where the product helper is called to handle all product page related preparation. In that method, prepareAndRender, they have the product loaded (line 261) for you and you can perform some checks against it in your plugin already. Also, there are a couple of checks before product is loaded for further page render in this method. This way you don’t need to handle different cases. Moreover, an explicit place where product layout is getting prepared is line 283:

$this->initProductLayout($resultPage, $product, $params);

in the same product helper, which is the method we have added a plugin.

But, if you don’t need to have the product loaded and it is enough for you to have the product ID only (from request parameters) for your business logic, then you’re good to go with a plugin Magento\Catalog\Controller\Product\View:execute() with an after method.

That’s basically all you need to have a custom layout handle added to the product page.

Got into trouble and couldn’t make it happen? Write your questions down below in the comments. We’ll be happy to help!