Magento2 | PWA | GraphQL

Magento 2 create new category EAV Image attribute with file upload and Display Image on Category page as well as on Category Top Menu


In this tutorial, you will learn how to Creating a new category EAV attribute with image upload button.

Let's start it by creating custom module.

Find Complete Module on Github

Create folder in app/code/Magelearn/CustomImage

Step 1: Create module registration file

Add registration.php file in it:
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_CustomImage', __DIR__);
Add composer.json file in it:
{
    "name": "magelearn/module-customimage",
    "description": "Magento2 image attachment in category add/edit admin form and display image on category page",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\CustomImage\\": ""
        }
    }
}
Add etc/module.xml file in it:
<?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="Magelearn_CustomImage" setup_version="1.0.0">
</module> </config>
Create a Data Patch file CreateCustomImageAttribute.php inside app/code/Magelearn/CustomImage/Setup/Patch/Data
<?php

declare(strict_types=1);

namespace Magelearn\CustomImage\Setup\Patch\Data;

use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Category\Attribute\Backend\Image;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class CreateCustomImageAttribute implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory
    ) {
    }

    public function apply(): void
    {
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create([
            'setup' => $this->moduleDataSetup,
        ]);

        $eavSetup->addAttribute(
            Category::ENTITY,
            'custom_image',
            [
                'type' => 'varchar',
                'label' => 'Custom Image',
                'input' => 'image',
                'backend' => Image::class,
                'required' => false,
                'sort_order' => 10,
                'global' => ScopedAttributeInterface::SCOPE_STORE,
                'group' => 'Content',
            ]
        );
    }

    public static function getDependencies(): array
    {
        return [];
    }

    public function getAliases(): array
    {
        return [];
    }
}
Here we can use Backend option and Input type as Image itself, we can control that later in UI component and di(Dependency Injection).

Create category_form.xml inside Magelearn/CustomImage/view/adminhtml/ui_component
<?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="custom_image" sortOrder="40" formElement="imageUploader">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">category</item>
                </item>
            </argument>
            <settings>
                <elementTmpl>ui/form/element/uploader/image</elementTmpl>
                <dataType>string</dataType>
                <label translate="true">Custom Category Image</label>
                <visible>true</visible>
                <required>false</required>
            </settings>
            <formElements>
                <imageUploader>
                    <settings>
                        <required>false</required>
                        <uploaderConfig>
                            <param xsi:type="url" name="url" path="catalog/category_image/upload"/>
                        </uploaderConfig>
                        <previewTmpl>Magento_Catalog/image-preview</previewTmpl>
                        <openDialogTitle>Media Gallery</openDialogTitle>
                        <initialMediaGalleryOpenSubpath>catalog/category</initialMediaGalleryOpenSubpath>
                        <allowedExtensions>jpg jpeg gif png</allowedExtensions>
                        <maxFileSize>4194304</maxFileSize>
                    </settings>
                </imageUploader>
            </formElements>
        </field>
    </fieldset>
</form>
Create di.xml file inside Magelearn/CustomImage/etc

Here we can include the file extension types inside allowedExtensions section.
<?xml version="1.0" encoding="UTF-8"?>
<config
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <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\Theme\Block\Html\Topmenu" type="Magelearn\CustomImage\Block\Html\Topmenu" />
</config>

Now it is all done! Once the code is run, an option to upload the file will be included. This is how the exact output looks like.


Now, we will check how to display this "custom_image" on Category view page.

Create catalog_category_view.xml file inside Magelearn/CustomImage/view/frontend/layout

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="category.view.container">
            <block class="Magento\Framework\View\Element\Template" name="magelearn.custom.image" template="Magelearn_CustomImage::image.phtml">
                <arguments>
                    <argument name="view_model" xsi:type="object">Magelearn\CustomImage\ViewModel\CategoryImage</argument>
                    <argument name="image_code" xsi:type="string">custom_image</argument>
                    <argument name="css_class" xsi:type="string">custom_image</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>
Here we define our view model as well our custom template file.
Now Create ViewModel file CategoryImage.php file inside Magelearn/CustomImage/ViewModel
<?php

declare(strict_types=1);

namespace Magelearn\CustomImage\ViewModel;

use Magelearn\CustomImage\Helper\Category as CategoryHelper;
use Magento\Catalog\Helper\Data as CatalogData;
use Magento\Catalog\Model\Category;
use Magento\Framework\View\Element\Block\ArgumentInterface;

class CategoryImage implements ArgumentInterface
{
    public function __construct(
        private readonly CatalogData $catalogData,
        private readonly CategoryHelper $categoryHelper
    ) {
    }

    public function getCategory(): ?Category
    {
        $category = $this->catalogData->getCategory();

        return $category instanceof Category ? $category : null;
    }

    public function getImageUrl(string $imageCode): ?string
    {
        $category = $this->getCategory();

        if (!$category) {
            return null;
        }

        $image = $category->getData($imageCode);

        if (!$image) {
            return null;
        }

        return $this->categoryHelper->getImageUrl($image);
    1
}
Also Create Helper file Category.php file inside Magelearn/CustomImage/Helper
<?php

namespace Magelearn\CustomImage\Helper;

use Magento\Framework\App\Helper\AbstractHelper;

class Category extends AbstractHelper
{
    /**
     * @return array
     */
    public function getAdditionalImageTypes()
    {
        return array('custom_image');
    }

    /**
     * Retrieve image URL
     * @param $image
     * @return string
     */
    public function getImageUrl($image)
    {
        $url = false;
        //$image = $this->getImage();
        if ($image) {
            if (is_string($image)) {
                $url = $this->_urlBuilder->getBaseUrl() . $image;
                //$url = $image;
            } else {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('Something went wrong while getting the image url.')
                );
            }
        }
        return $url;
    }

}
And finally, Create templete file image.phtml inside Magelearn/CustomImage/view/frontend/templates
<?php

declare(strict_types=1);

/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magelearn\CustomImage\ViewModel\CategoryImage $viewModel */

$viewModel = $block->getViewModel();
$category = $viewModel->getCategory();

if (!$category) {
    return;
}

$imageCode = (string) $block->getData('image_code');
$imageUrl = $viewModel->getImageUrl($imageCode);
$cssClass = (string) $block->getData('css_class');

if (!$imageUrl) {
    return;
}

$categoryName = $block->escapeHtml($category->getName());
?>

<div class="<?= $block->escapeHtmlAttr($cssClass) ?>">
    <img
        src="<?= $block->escapeUrl($imageUrl) ?>"
        alt="<?= $categoryName ?>"
        title="<?= $categoryName ?>"
    />
</div>
This will display on front end as per below:

Now we will check another interesting part of this blog.
Where we will display this custom image on Category Top Menu.

Modify di.xml file inside Magelearn/CustomImage/etc
<?xml version="1.0" encoding="UTF-8"?>
<config
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <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\Theme\Block\Html\Topmenu" type="Magelearn\CustomImage\Block\Html\Topmenu" />
</config>
Create file 
Topmenu.php inside Magelearn/CustomImage/Block/Html
<?php
namespace Magelearn\CustomImage\Block\Html;
  
use Magento\Framework\Data\Tree\Node;
use Magento\Framework\DataObject;
use Magento\Framework\View\Element\Template;
use Magento\Framework\Data\Tree\NodeFactory;
use Magento\Framework\Data\TreeFactory;
  
class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{
    protected $_categoryFactory;
    protected $_storeManager;
     
    public function __construct(
        Template\Context $context,
        NodeFactory $nodeFactory,
        TreeFactory $treeFactory,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collecionFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        array $data = []
        ) {
            parent::__construct($context, $nodeFactory, $treeFactory, $data);
            $this->_categoryFactory = $collecionFactory;
            $this->_storeManager = $storeManager;
         
    }
 
    protected function _getHtml(
        \Magento\Framework\Data\Tree\Node $menuTree,
        $childrenWrapClass,
        $limit,
        array $colBrakes = []
    ) {
        $html = '';
 
        $children = $menuTree->getChildren();
        $parentLevel = $menuTree->getLevel();
        $childLevel = $parentLevel === null ? 0 : $parentLevel + 1;
 
        $counter = 1;
        $itemPosition = 1;
        $childrenCount = $children->count();
 
        $parentPositionClass = $menuTree->getPositionClass();
        $itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';
 
        /** @var \Magento\Framework\Data\Tree\Node $child */
        foreach ($children as $child) {
            if ($childLevel === 0 && $child->getData('is_parent_active') === false) {
                continue;
            }
            $child->setLevel($childLevel);
            $child->setIsFirst($counter == 1);
            $child->setIsLast($counter == $childrenCount);
            $child->setPositionClass($itemPositionClassPrefix . $counter);
 
            $outermostClassCode = '';
            $outermostClass = $menuTree->getOutermostClass();
 
            if ($childLevel == 0 && $outermostClass) {
                $outermostClassCode = ' class="' . $outermostClass . '" ';
                $currentClass = $child->getClass();
 
                if (empty($currentClass)) {
                    $child->setClass($outermostClass);
                } else {
                    $child->setClass($currentClass . ' ' . $outermostClass);
                }
            }
 
            if (is_array($colBrakes) && count($colBrakes) && $colBrakes[$counter]['colbrake']) {
                $html .= '</ul></li><li class="column"><ul>';
            }
             
            $html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
            $html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>' . $this->escapeHtml(
                $child->getName()
                ) . $this->getCustomImage($child) . '</span></a>' . $this->_addSubMenu(
                $child,
                $childLevel,
                $childrenWrapClass,
                $limit
            ) . '</li>';
            $itemPosition++;
            $counter++;
        }
 
        if (is_array($colBrakes) && count($colBrakes) && $limit) {
            $html = '<li class="column"><ul>' . $html . '</ul></li>';
        }
 
        return $html;
    }
 
    public function getCustomImage($childObj)
    {
        if (!($childObj->getIsCategory() && $childObj->getLevel() == 1)) {
            return false;
        }
 
        $store = $this->_storeManager->getStore();
        $mediaBaseUrl = $store->getBaseUrl();
 
        $catNodeArr = explode('-', $childObj->getId());
        $catId = end($catNodeArr);
         
        $collection = $this->_categoryFactory
                ->create()
                ->addAttributeToSelect('custom_image')
                ->addAttributeToFilter('entity_id',['eq'=>$catId])
                ->setPageSize(1);
         
        if ($collection->getSize() && $collection->getFirstItem()->getCustomImage()) {
            $catCustomImageUrl = $mediaBaseUrl
                        . $collection->getFirstItem()->getCustomImage();
 
            return '<span class="cat-thumbnail"><img src="'.$catCustomImageUrl.'"></span>';
        }
    }
 
}
It will display on Category top menu as per below:


0 Comments On "Magento 2 create new category EAV Image attribute with file upload and Display Image on Category page as well as on Category Top Menu"

Back To Top