Add Category Thumbnail Images in Magento 2

Whilst Magento provides a default image attribute assigned to categories, it is common for developers to want to add additional image attributes to categories. For example, to create a thumbnail image that might appear on the home page alongside other thumbnail images and link to the category pages. To add category thumbnail images in Magento 2, follow the steps below.

Firstly, create the directories for a custom module along with the module’s registration.php and module.xml files.

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Vendor]_[Module]',
    __DIR__
);
<?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="1.0.0" />
</config>

Run the enable module and database upgrade commands within your Magento 2 root directory.

$ /path/to/your/php bin/magento module:enable [Vendor]_[Module]
$ /path/to/your/php bin/magento setup:upgrade

Add an InstallData.php class which will be responsible for adding the category attribute.

In this example, the attribute code used will be thumbnail_image. Note the thumbnail image attribute’s backend type: Magento\Catalog\Model\Category\Attribute\Backend\Image.

<?php
namespace [Vendor]\[Module]\Setup;

use Magento\Catalog\Setup\CategorySetupFactory;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory.
     *
     * @var EavSetupFactory
     */
    private $_eavSetupFactory;

    /**
     * @var CategorySetupFactory
     */
    protected $categorySetupFactory;

    /**
     * InstallData constructor.
     *
     * @param EavSetupFactory $eavSetupFactory
     * @param CategorySetupFactory $categorySetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory,
        CategorySetupFactory $categorySetupFactory
    )
    {
        $this->_eavSetupFactory = $eavSetupFactory;
        $this->categorySetupFactory = $categorySetupFactory;
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        /** @var EavSetup $eavSetup */
        $setup = $this->categorySetupFactory->create(['setup' => $setup]);
        $setup->addAttribute(
            \Magento\Catalog\Model\Category::ENTITY, 'thumbnail_image', [
                'type' => 'varchar',
                'label' => 'Thumbnail Image',
                'input' => 'image',
                'backend' => 'Magento\Catalog\Model\Category\Attribute\Backend\Image',
                'required' => false,
                'sort_order' => 9,
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
                'group' => 'General Information',
            ]
        );
    }
}

In the Categories section within the admin, the fields are rendered using a form UI component. Therefore in order to add a custom attribute to this form, create a category_form.xml file and define the custom attribute field.

Ensure that the field_name value matches the attribute code of your custom attribute.

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <fieldset name="content">
        <field name="thumbnail_image">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="source" xsi:type="string">category</item>
                    <item name="label" xsi:type="string" translate="true">Thumbnail Image</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="formElement" xsi:type="string">fileUploader</item>
                    <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
                    <item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
                    <item name="required" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="number">40</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="[module]/thumbnail/upload"/>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

Note the path defined within the uplaoderConfig item. This will be the URL that will handle the upload of the thumbnail image attribute.

Define the route to handle the upload within a routes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="[module]" frontName="[module]">
            <module name="[Vendor]_[Module" before="Magento_Backend" />
        </route>
    </router>
</config>

And proceed with adding the controller class.

<?php
namespace Vendor\Module\Controller\Adminhtml\Thumbnail;

use Magento\Framework\Controller\ResultFactory;

class Upload extends \Magento\Backend\App\Action
{
    /**
     * Image uploader
     *
     * @var \Magento\Catalog\Model\ImageUploader
     */
    protected $imageUploader;

    /**
     * Uploader factory
     *
     * @var \Magento\MediaStorage\Model\File\UploaderFactory
     */
    private $uploaderFactory;

    /**
     * Media directory object (writable).
     *
     * @var \Magento\Framework\Filesystem\Directory\WriteInterface
     */
    protected $mediaDirectory;

    /**
     * Store manager
     *
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * Core file storage database
     *
     * @var \Magento\MediaStorage\Helper\File\Storage\Database
     */
    protected $coreFileStorageDatabase;

    /**
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;

    /**
     * Upload constructor.
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Catalog\Model\ImageUploader $imageUploader
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Catalog\Model\ImageUploader $imageUploader,
        \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
        \Psr\Log\LoggerInterface $logger
    ) {
        parent::__construct($context);
        $this->imageUploader = $imageUploader;
        $this->uploaderFactory = $uploaderFactory;
        $this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
        $this->storeManager = $storeManager;
        $this->coreFileStorageDatabase = $coreFileStorageDatabase;
        $this->logger = $logger;
    }

    /**
     * Upload file controller action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        try {
            $result = $this->imageUploader->saveFileToTmpDir('thumbnail_image');
            $result['cookie'] = [
                'name' => $this->_getSession()->getName(),
                'value' => $this->_getSession()->getSessionId(),
                'lifetime' => $this->_getSession()->getCookieLifetime(),
                'path' => $this->_getSession()->getCookiePath(),
                'domain' => $this->_getSession()->getCookieDomain(),
            ];
        } catch (\Exception $e) {
            $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
        }
        return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
    }
}

Now create the module’s di.xml file and define the imageUploader class to use to handle the upload. In addition, add arguments to the imageUploader to define a baseTmpPath, basePath and the image allowedExtensions.

<?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="[Vendor]\[Module]\Controller\Adminhtml\Thumbnail\Upload">
        <arguments>
            <argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
        </arguments>
    </type>
    <virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
        <arguments>
            <argument name="baseTmpPath" xsi:type="string">catalog/tmp/category</argument>
            <argument name="basePath" xsi:type="string">catalog/category</argument>
            <argument name="allowedExtensions" xsi:type="array">
                <item name="jpg" xsi:type="string">jpg</item>
                <item name="jpeg" xsi:type="string">jpeg</item>
                <item name="gif" xsi:type="string">gif</item>
                <item name="png" xsi:type="string">png</item>
            </argument>
        </arguments>
    </virtualType>
</config>

Within the Magento\Catalog\Model\Category\DataProvider class, a getFieldsMap() method exists that returns the fields associated to a category.

<?php
namespace Magento\Catalog\Model\Category;

....

class DataProvider

    ....

    protected function getFieldsMap()
    {
        return [
            'general' => [
                    'parent',
                    'path',
                    'is_active',
                    'include_in_menu',
                    'name',
                ],
            'content' => [
                    'image',
                    'description',
                    'landing_page',
                ],
            'display_settings' => [
                    'display_mode',
                    'is_anchor',
                    'available_sort_by',
                    'use_config.available_sort_by',
                    'default_sort_by',
                    'use_config.default_sort_by',
                    'filter_price_range',
                    'use_config.filter_price_range',
                ],
            'search_engine_optimization' => [
                    'url_key',
                    'url_key_create_redirect',
                    'url_key_group',
                    'meta_title',
                    'meta_keywords',
                    'meta_description',
                ],
            'assign_products' => [
                ],
            'design' => [
                    'custom_use_parent_settings',
                    'custom_apply_to_products',
                    'custom_design',
                    'page_layout',
                    'custom_layout_update',
                ],
            'schedule_design_update' => [
                    'custom_design_from',
                    'custom_design_to',
                ],
            'category_view_optimization' => [
                ],
            'category_permissions' => [
                ],
        ];
    }

    ....
}

This method will need to be overridden in order to the thumbnail_image field.

Edit the custom module’s di.xml file and add a <preference> for this class.

<?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="[Vendor]\[Module]\Controller\Adminhtml\Thumbnail\Upload">
        <arguments>
            <argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
        </arguments>
    </type>
    <virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
        <arguments>
            <argument name="baseTmpPath" xsi:type="string">catalog/tmp/category</argument>
            <argument name="basePath" xsi:type="string">catalog/category</argument>
            <argument name="allowedExtensions" xsi:type="array">
                <item name="jpg" xsi:type="string">jpg</item>
                <item name="jpeg" xsi:type="string">jpeg</item>
                <item name="gif" xsi:type="string">gif</item>
                <item name="png" xsi:type="string">png</item>
            </argument>
        </arguments>
    </virtualType>
    <preference for="Magento\Catalog\Model\Category\DataProvider" type="[Vendor]\[Module]\Model\Category\DataProvider" />
</config>

Add the overriding class, and implement the getFieldsMap() method that adds the thumbnail_image to the array.

<?php
namespace [Vendor]\[Module]\Model\Category;

use Magento\Catalog\Model\Category\DataProvider as CategoryDataProvider;

class DataProvider extends CategoryDataProvider
{
    /**
     * @return array
     */
    protected function getFieldsMap()
    {
        $fields = parent::getFieldsMap();
        $fields['content'][] = 'thumbnail_image';

        return $fields;
    }
}

Refresh the cache and you should notice that the thumbnail image attribute has been added to the category form.

Add Category Thumbnail Images in Magento 2

Note: This article is based on Magento Open Source version 2.2.