Back to Top

Custom Linked Product Types Magento2

Updated 26 August 2021

Let’s suppose we need to add a product collection on the product view page and we want the admin to add those products on the basis of some specifications, so for that, we can use custom linked product types like Related products.

Today we are going to see how can we create a custom product link in Magento 2. So, first of all, create basic module files like module.xml and resgistration.php to register the module (in this tutorial our module’s name will be Webkul_CustomLink).

Now, to create a custom product link type, we need to add its entry in catalog_product_link_type and catalog_product_link_attribute tables, then create modifier (to add it on product edit page), model (to get linked product collection from catalog product model), and ui_component.

Develop Data Patch for Custom Linked Product Types

So let’s begin with schema patch, create a file –

Webkul/CustomLink/Setup/Patch/Data/CreateLink.php

The schema patch file will make an entry for our custom linked product type in the database.

Searching for an experienced
Magento 2 Company ?
Find out More
<?php
namespace Webkul\CustomLink\Setup\Patch\Data;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class CreateLink implements DataPatchInterface
{
    /**
     * @param ModuleDataSetupInterface $moduleDataSetup
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
    }

    public function apply()
    {
        $setup = $this->moduleDataSetup;

        $data = [
            [
                'link_type_id' => \Webkul\CustomLink\Model\Product\Link::LINK_TYPE_CUSTOMLINK,
                'code' => 'customlink'
            ],
        ];

        foreach ($data as $bind) {
            $setup->getConnection()
                ->insertForce($setup->getTable('catalog_product_link_type'), $bind);
        }
        $data = [
            [
                'link_type_id' => \Webkul\CustomLink\Model\Product\Link::LINK_TYPE_CUSTOMLINK,
                'product_link_attribute_code' => 'position',
                'data_type' => 'int',
            ]
        ];
        $setup->getConnection()
            ->insertMultiple($setup->getTable('catalog_product_link_attribute'), $data);
    }

    /**
     * {@inheritdoc}
     */
    public static function getDependencies()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getAliases()
    {
        return [];
    }
}

Create Custom Linked Product Types Model

Now create custom link products model file –

Webkul/CustomLink/Model/Product.php

<?php
namespace Webkul\CustomLink\Model;

class Product extends \Magento\Catalog\Model\Product
{
    const LINK_TYPE = 'customlink';
    const LINK_TYPE_CUSTOMLINK = 7;

    /**
     * Retrieve array of customlink products
     *
     * @return array
     */
    public function getCustomLinkProducts()
    {
        if (!$this->hasCustomLinkProducts()) {
            $products = [];
            $collection = $this->getCustomLinkProductCollection();
            foreach ($collection as $product) {
                $products[] = $product;
            }
            $this->setCustomLinkProducts($products);
        }
        return $this->getData('custom_link_products');
    }

    /**
     * Retrieve customlink products identifiers
     *
     * @return array
     */
    public function getCustomLinkProductIds()
    {
        if (!$this->hasCustomLinkProductIds()) {
            $ids = [];
            foreach ($this->getCustomLinkProducts() as $product) {
                $ids[] = $product->getId();
            }
            $this->setCustomLinkProductIds($ids);
        }
        return [$this->getData('custom_link_product_ids')];
    }

    /**
     * Retrieve collection customlink product
     *
     * @return \Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection
     */
    public function getCustomLinkProductCollection()
    {
        $collection = $this->getLinkInstance()->setLinkTypeId(
            static::LINK_TYPE_CUSTOMLINK
        )->getProductCollection()->setIsStrongMode();
        $collection->setProduct($this);

        return $collection;
    }
}

Inject LinkProvider Dependencies

To create a custom product link, we need to inject some dependencies to core link provider classes, so create the following di.xml for that.

Webkul/CustomLink/etc/di.xml

Inject required dependencies in the di.xml file.

<?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\Model\Product\LinkTypeProvider">
        <arguments>
            <argument name="linkTypes" xsi:type="array">
                <item name="customlink" xsi:type="const">Webkul\CustomLink\Model\Product\Link::LINK_TYPE_CUSTOMLINK</item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\Catalog\Model\ProductLink\CollectionProvider">
        <arguments>
            <argument name="providers" xsi:type="array">
                <item name="customlink" xsi:type="object">Webkul\CustomLink\Model\ProductLink\CollectionProvider\CustomLinkProducts</item>
            </argument>
        </arguments>
    </type>
    <!-- Plugin to add update argument in collectionProvider -->
    <type name="Webkul\CustomLink\Model\ProductLink\CollectionProvider\CustomLinkProducts">
        <plugin name="change_custom_link_before" type="Webkul\CustomLink\Plugin\UpdateToCustomLinkModel"/>
    </type>
</config>

Create Injected Model files

Create the following model files –

Webkul/CustomLink/Model/Product/Link.php

Link provider class.

<?php
/**

 * Webkul Software.
 *
 * @category  Webkul
 * @package   Webkul_CustomLink
 * @author    Webkul
 * @copyright Copyright (c) Webkul Software Private Limited (https://webkul.com)
 * @license   https://store.webkul.com/license.html
 */

namespace Webkul\CustomLink\Model\Product;

class Link extends \Magento\Catalog\Model\Product\Link
{
    const LINK_TYPE_CUSTOMLINK = 7;
}

Webkul/CustomLink/Model/ProductLink/CollectionProvider/CustomLinkProducts.php

The custom link collection provider class.

<?php
namespace Webkul\CustomLink\Model\ProductLink\CollectionProvider;

class CustomLinkProducts
{
    public function getLinkedProducts($product)
    {
        return $product->getCustomLinkProducts();
    }
}

Webkul/CustomLink/Plugin/UpdateToCustomLinkModel.php

In the above CollectionProvider, the $product param in the getLinkedProducts function is an instance of Magento\Catalog\Model\Product, we need to update it to our custom product link model, i.e., Webkul\CustomLink\Model\Product.

<?php
namespace Webkul\CustomLink\Plugin;

class UpdateToCustomLinkModel
{
    /**
     * @param \Webkul\CustomLink\Model\ProductFactory $catalogModel
     */
    public function __construct(
        \Webkul\CustomLink\Model\ProductFactory $catalogModel
    ) {
        $this->catalogModel = $catalogModel;
    }
    
    /**
     * Before plugin to update model class
     *
     * @param \Webkul\CustomLink\Model\ProductLink\CollectionProvider\CustomLinkProducts $subject
     * @param Object $product
     * @return array
     */
    public function beforeGetLinkedProducts(
        \Webkul\CustomLink\Model\ProductLink\CollectionProvider\CustomLinkProducts $subject,
        $product
    ) {
        $currentProduct = $this->catalogModel->create()->load($product->getId());
        return [$currentProduct];
    }
}

Create UI grid for Custom Linked Product Type

Now we are left with two parts, first one is ui_component to show columns, filters, etc. and the second one is modifiers to add dynamic data & validations to ui_component values. So for that, we need to create the following di.xml, modifier file, and component file.

Webkul/CustomLink/etc/adminhtml/di.xml

Inject our modifier class to the modifiers pool.

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="customlink" xsi:type="array">
                    <item name="class" xsi:type="string">Webkul\CustomLink\Ui\DataProvider\Product\Form\Modifier\CustomLinkTab</item>
                    <item name="sortOrder" xsi:type="number">7</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

Webkul/CustomLink/Ui/DataProvider/Product/Form/Modifier/CustomLinkTab.php

Here I am creating a whole modifier class just to elaborate how inner grids are working, you can simply add a plugin class also of Related product’s modifier and create afterModifyMeta & afterModifyData methods to customize UI.

<?php
namespace Webkul\CustomLink\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Modal;
use Magento\Framework\Phrase;
use Magento\Ui\Component\DynamicRows;
use Magento\Ui\Component\Form\Element\DataType\Number;
use Magento\Ui\Component\Form\Element\DataType\Text;
use Magento\Ui\Component\Form\Field;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\ProductLinkInterface;
use Magento\Catalog\Api\ProductLinkRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Eav\Api\AttributeSetRepositoryInterface;

class CustomLinkTab extends AbstractModifier
{
    const DATA_SCOPE_CUSTOM = 'customlink';
    const GROUP_CUSTOM = 'customlink';
    
    protected $meta = [];
    protected $scopeName = 'product_form.product_form';
    protected $scopePrefix = '';
    private $priceModifier;

    /**
     * @param LocatorInterface $locator
     * @param ArrayManager $arrayManager
     * @param UrlInterface $urlBuilder
     * @param \Magento\Catalog\Model\ProductFactory $_productloader
     * @param \Magento\Backend\Model\UrlInterface $backendUrl
     */
    public function __construct(
        LocatorInterface $locator,
        ArrayManager $arrayManager,
        UrlInterface $urlBuilder,
        ProductLinkRepositoryInterface $productLinkRepository,
        ProductRepositoryInterface $productRepository,
        ImageHelper $imageHelper,
        Status $status,
        AttributeSetRepositoryInterface $attributeSetRepository,
        \Magento\Catalog\Model\ProductFactory $_productloader,
        \Magento\Backend\Model\UrlInterface $backendUrl
    ) {
        $this->locator = $locator;
        $this->arrayManager = $arrayManager;
        $this->productLinkRepository = $productLinkRepository;
        $this->productRepository = $productRepository;
        $this->imageHelper = $imageHelper;
        $this->status = $status;
        $this->attributeSetRepository = $attributeSetRepository;
        $this->urlBuilder = $urlBuilder;
        $this->_productloader = $_productloader;
        $this->_backendUrl = $backendUrl;
    }

    public function modifyData(array $data)
    {
        /** @var \Magento\Catalog\Model\Product $product */
        $product = $this->locator->getProduct();
        $productId = $product->getId();

        if (!$productId) {
            return $data;
        }

        $priceModifier = $this->getPriceModifier();
        /**
         * Set field name for modifier
         */
        $priceModifier->setData('name', 'price');
        $dataScope = static::DATA_SCOPE_CUSTOM;

        $data[$productId]['links'][$dataScope] = [];
        foreach ($this->productLinkRepository->getList($product) as $linkItem) {
            if ($linkItem->getLinkType() !== $dataScope) {
                continue;
            }

            /** @var \Magento\Catalog\Model\Product $linkedProduct */
            $linkedProduct = $this->productRepository->get(
                $linkItem->getLinkedProductSku(),
                false,
                $this->locator->getStore()->getId()
            );
            $data[$productId]['links'][$dataScope][] = $this->fillData($linkedProduct, $linkItem);
        }
        if (!empty($data[$productId]['links'][$dataScope])) {
            $dataMap = $priceModifier->prepareDataSource([
                'data' => [
                    'items' => $data[$productId]['links'][$dataScope]
                ]
            ]);
            $data[$productId]['links'][$dataScope] = $dataMap['data']['items'];
        }

        $data[$productId][self::DATA_SOURCE_DEFAULT]['current_product_id'] = $productId;
        $data[$productId][self::DATA_SOURCE_DEFAULT]['current_store_id'] = $this->locator->getStore()->getId();

        return $data;
    }

    private function getPriceModifier()
    {
        if (!$this->priceModifier) {
            $this->priceModifier = ObjectManager::getInstance()->get(
                \Magento\Catalog\Ui\Component\Listing\Columns\Price::class
            );
        }
        return $this->priceModifier;
    }
  
    public function modifyMeta(array $meta)
    {
        $this->meta = $meta;
        $this->addCustomTab();
  
        return $this->meta;
    }
  
    protected function addCustomTab()
    {
        $this->meta = array_merge_recursive(
            $this->meta,
            [
                static::GROUP_CUSTOM => $this->getTabConfig(),
            ]
        );
    }
  
    protected function getTabConfig()
    {
        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'label' => __('Custom Link Products'),
                        'componentType' => Fieldset::NAME,
                        'sortOrder' => 40,
                        'dataScope' => '',
                        'provider' => static::FORM_NAME . '.product_form_data_source',
                        'ns' => static::FORM_NAME,
                        'collapsible' => true,
                    ],
                ],
            ],
            'children' => [
                $this->scopePrefix . static::DATA_SCOPE_CUSTOM => $this->getCustomFieldset(),
            ],
        ];
    }

    /**
     * Prepare data column
     *
     * @param ProductInterface $linkedProduct
     * @param ProductLinkInterface $linkItem
     * @return array
     * @since 101.0.0
     */
    protected function fillData(ProductInterface $linkedProduct, ProductLinkInterface $linkItem)
    {
        return [
            'id' => $linkedProduct->getId(),
            'thumbnail' => $this->imageHelper->init($linkedProduct, 'product_listing_thumbnail')->getUrl(),
            'name' => $linkedProduct->getName(),
            'status' => $this->status->getOptionText($linkedProduct->getStatus()),
            'attribute_set' => $this->attributeSetRepository
                ->get($linkedProduct->getAttributeSetId())
                ->getAttributeSetName(),
            'sku' => $linkItem->getLinkedProductSku(),
            'price' => $linkedProduct->getPrice(),
            'position' => $linkItem->getPosition(),
        ];
    }

    protected function getCustomFieldset()
    {
        $content = __(
            'Custom Link products are shown to customers as addons to the item the customer is looking at.'
        );

        return [
            'children' => [
                'button_set' => $this->getButtonSet(
                    $content,
                    __('Add Custom Link Products'),
                    $this->scopePrefix . static::DATA_SCOPE_CUSTOM
                ),
                'modal' => $this->getGenericModal(
                    __('Add Custom Link Products'),
                    $this->scopePrefix . static::DATA_SCOPE_CUSTOM
                ),
                static::DATA_SCOPE_CUSTOM => $this->getGrid($this->scopePrefix . static::DATA_SCOPE_CUSTOM),
            ],
            'arguments' => [
                'data' => [
                    'config' => [
                        'additionalClasses' => 'admin__fieldset-section',
                        'label' => __('Add Custom Link Products'),
                        'collapsible' => false,
                        'componentType' => Fieldset::NAME,
                        'dataScope' => '',
                        'sortOrder' => 10,
                    ],
                ],
            ]
        ];
    }

    /**
     * Retrieve button set
     *
     * @param Phrase $content
     * @param Phrase $buttonTitle
     * @param string $scope
     * @return array
     * @since 101.0.0
     */
    protected function getButtonSet(Phrase $content, Phrase $buttonTitle, $scope)
    {
        $modalTarget = $this->scopeName . '.' . static::GROUP_CUSTOM . '.' . $scope . '.modal';

        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'formElement' => 'container',
                        'componentType' => 'container',
                        'label' => false,
                        'content' => $content,
                        'template' => 'ui/form/components/complex',
                    ],
                ],
            ],
            'children' => [
                'button_' . $scope => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'formElement' => 'container',
                                'componentType' => 'container',
                                'component' => 'Magento_Ui/js/form/components/button',
                                'actions' => [
                                    [
                                        'targetName' => $modalTarget,
                                        'actionName' => 'toggleModal',
                                    ],
                                    [
                                        'targetName' => $modalTarget . '.' . $scope . '_product_listing',
                                        'actionName' => 'render',
                                    ]
                                ],
                                'title' => $buttonTitle,
                                'provider' => null,
                            ],
                        ],
                    ],

                ],
            ],
        ];
    }

    /**
     * Prepares config for modal slide-out panel
     *
     * @param Phrase $title
     * @param string $scope
     * @return array
     * @since 101.0.0
     */
    protected function getGenericModal(Phrase $title, $scope)
    {
        $listingTarget = $scope . '_product_listing';

        $modal = [
            'arguments' => [
                'data' => [
                    'config' => [
                        'componentType' => Modal::NAME,
                        'dataScope' => '',
                        'options' => [
                            'title' => $title,
                            'buttons' => [
                                [
                                    'text' => __('Cancel'),
                                    'actions' => [
                                        'closeModal'
                                    ]
                                ],
                                [
                                    'text' => __('Add Selected Products'),
                                    'class' => 'action-primary',
                                    'actions' => [
                                        [
                                            'targetName' => 'index = ' . $listingTarget,
                                            'actionName' => 'save'
                                        ],
                                        'closeModal'
                                    ]
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'children' => [
                $listingTarget => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'autoRender' => false,
                                'componentType' => 'insertListing',
                                'dataScope' => $listingTarget,
                                'externalProvider' => $listingTarget . '.' . $listingTarget . '_data_source',
                                'selectionsProvider' => $listingTarget . '.' . $listingTarget . '.product_columns.ids',
                                'ns' => $listingTarget,
                                'render_url' => $this->urlBuilder->getUrl('mui/index/render'),
                                'realTimeLink' => true,
                                'dataLinks' => [
                                    'imports' => false,
                                    'exports' => true
                                ],
                                'behaviourType' => 'simple',
                                'externalFilterMode' => true,
                                'imports' => [
                                    'productId' => '${ $.provider }:data.product.current_product_id',
                                    'storeId' => '${ $.provider }:data.product.current_store_id',
                                    '__disableTmpl' => ['productId' => false, 'storeId' => false],
                                ],
                                'exports' => [
                                    'productId' => '${ $.externalProvider }:params.current_product_id',
                                    'storeId' => '${ $.externalProvider }:params.current_store_id',
                                    '__disableTmpl' => ['productId' => false, 'storeId' => false],
                                ]
                            ],
                        ],
                    ],
                ],
            ],
        ];

        return $modal;
    }

    /**
     * Retrieve grid
     *
     * @param string $scope
     * @return array
     */
    protected function getGrid($scope)
    {
        $dataProvider = $scope . '_product_listing';

        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'additionalClasses' => 'admin__field-wide',
                        'componentType' => DynamicRows::NAME,
                        'label' => null,
                        'columnsHeader' => false,
                        'columnsHeaderAfterRender' => true,
                        'renderDefaultRecord' => false,
                        'template' => 'ui/dynamic-rows/templates/grid',
                        'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid',
                        'addButton' => false,
                        'recordTemplate' => 'record',
                        'dataScope' => 'data.links',
                        'deleteButtonLabel' => __('Remove'),
                        'dataProvider' => $dataProvider,
                        'map' => [
                            'id' => 'entity_id',
                            'name' => 'name',
                            'status' => 'status_text',
                            'attribute_set' => 'attribute_set_text',
                            'sku' => 'sku',
                            'price' => 'price',
                            'thumbnail' => 'thumbnail_src',
                        ],
                        'links' => [
                            'insertData' => '${ $.provider }:${ $.dataProvider }',
                            '__disableTmpl' => ['insertData' => false],
                        ],
                        'sortOrder' => 2,
                    ],
                ],
            ],
            'children' => [
                'record' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'componentType' => 'container',
                                'isTemplate' => true,
                                'is_collection' => true,
                                'component' => 'Magento_Ui/js/dynamic-rows/record',
                                'dataScope' => '',
                            ],
                        ],
                    ],
                    'children' => $this->fillMeta(),
                ],
            ],
        ];
    }

    /**
     * Retrieve meta column
     *
     * @return array
     * @since 101.0.0
     */
    protected function fillMeta()
    {
        return [
            'id' => $this->getTextColumn('id', false, __('ID'), 0),
            'thumbnail' => [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'componentType' => Field::NAME,
                            'formElement' => Input::NAME,
                            'elementTmpl' => 'ui/dynamic-rows/cells/thumbnail',
                            'dataType' => Text::NAME,
                            'dataScope' => 'thumbnail',
                            'fit' => true,
                            'label' => __('Thumbnail'),
                            'sortOrder' => 10,
                        ],
                    ],
                ],
            ],
            'name' => $this->getTextColumn('name', false, __('Name'), 20),
            'status' => $this->getTextColumn('status', true, __('Status'), 30),
            'attribute_set' => $this->getTextColumn('attribute_set', false, __('Attribute Set'), 40),
            'sku' => $this->getTextColumn('sku', true, __('SKU'), 50),
            'price' => $this->getTextColumn('price', true, __('Price'), 60),
            'actionDelete' => [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'additionalClasses' => 'data-grid-actions-cell',
                            'componentType' => 'actionDelete',
                            'dataType' => Text::NAME,
                            'label' => __('Actions'),
                            'sortOrder' => 70,
                            'fit' => true,
                        ],
                    ],
                ],
            ],
            'position' => [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'dataType' => Number::NAME,
                            'formElement' => Input::NAME,
                            'componentType' => Field::NAME,
                            'dataScope' => 'position',
                            'sortOrder' => 80,
                            'visible' => false,
                        ],
                    ],
                ],
            ],
        ];
    }

    /**
     * Retrieve text column structure
     *
     * @param string $dataScope
     * @param bool $fit
     * @param Phrase $label
     * @param int $sortOrder
     * @return array
     * @since 101.0.0
     */
    protected function getTextColumn($dataScope, $fit, Phrase $label, $sortOrder)
    {
        $column = [
            'arguments' => [
                'data' => [
                    'config' => [
                        'componentType' => Field::NAME,
                        'formElement' => Input::NAME,
                        'elementTmpl' => 'ui/dynamic-rows/cells/text',
                        'component' => 'Magento_Ui/js/form/element/text',
                        'dataType' => Text::NAME,
                        'dataScope' => $dataScope,
                        'fit' => $fit,
                        'label' => $label,
                        'sortOrder' => $sortOrder,
                    ],
                ],
            ],
        ];

        return $column;
    }
}

Webkul/CustomLink/view/adminhtml/ui_component/customlink_product_listing.xml

As because the UI target is set to $scope.’_product_listing’ in the modifier class (where $scope is our custom product link type code), so we need to create the customlink_product_listing.xml component file.

<?xml version="1.0" encoding="UTF-8"?>

<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">customlink_product_listing.customlink_product_listing_data_source</item>
            <item name="deps" xsi:type="string">customlink_product_listing.customlink_product_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">product_columns</item>
    </argument>
    <dataSource name="customlink_product_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Webkul\CustomLink\Ui\DataProvider\Product\CustomLinkDataProvider</argument>
            <argument name="name" xsi:type="string">customlink_product_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="cacheRequests" xsi:type="boolean">false</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="statefull" xsi:type="array">
                        <item name="applied" xsi:type="boolean">false</item>
                    </item>
                    <item name="params" xsi:type="array">
                        <item name="filters_modifier" xsi:type="array"/>
                    </item>
                    <item name="observers" xsi:type="array">
                        <item name="filters" xsi:type="object">Magento\Catalog\Ui\Component\Listing\Filters</item>
                    </item>
                </item>
            </argument>
        </filters>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="product_columns" class="Magento\Ui\Component\Listing\Columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">customGrid</item>
                        <item name="target" xsi:type="string">selectData</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <selectionsColumn name="ids" sortOrder="0">
            <settings>
                <indexField>entity_id</indexField>
                <preserveSelectionsOnFilter>true</preserveSelectionsOnFilter>
            </settings>
        </selectionsColumn>
        <column name="entity_id" sortOrder="10">
            <settings>
                <filter>textRange</filter>
                <label translate="true">ID</label>
                <sorting>asc</sorting>
            </settings>
        </column>
        <column name="thumbnail" class="Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail" component="Magento_Ui/js/grid/columns/thumbnail" sortOrder="20">
            <settings>
                <altField>name</altField>
                <hasPreview>1</hasPreview>
                <addField>true</addField>
                <label translate="true">Thumbnail</label>
                <sortable>false</sortable>
            </settings>
        </column>
        <column name="name" sortOrder="30">
            <settings>
                <addField>true</addField>
                <filter>text</filter>
                <label translate="true">Name</label>
            </settings>
        </column>
        <column name="attribute_set_id" component="Magento_Ui/js/grid/columns/select" sortOrder="40">
            <settings>
                <options class="Magento\Catalog\Model\Product\AttributeSet\Options"/>
                <filter>select</filter>
                <dataType>select</dataType>
                <label translate="true">Attribute Set</label>
            </settings>
        </column>
        <column name="attribute_set_text" class="Magento\Catalog\Ui\Component\Listing\Columns\AttributeSetText" sortOrder="41">
            <settings>
                <label translate="true">AttributeSetText</label>
                <visible>false</visible>
            </settings>
        </column>
        <column name="status" component="Magento_Ui/js/grid/columns/select" sortOrder="50">
            <settings>
                <options class="Magento\Catalog\Model\Product\Attribute\Source\Status"/>
                <filter>select</filter>
                <dataType>select</dataType>
                <label translate="true">Status</label>
            </settings>
        </column>
        <column name="status_text" class="Magento\Catalog\Ui\Component\Listing\Columns\StatusText" sortOrder="51">
            <settings>
                <label translate="true">StatusText</label>
                <visible>false</visible>
            </settings>
        </column>
        <column name="type_id" component="Magento_Ui/js/grid/columns/select" sortOrder="60">
            <settings>
                <options class="Magento\Catalog\Model\Product\Type"/>
                <filter>select</filter>
                <dataType>select</dataType>
                <label translate="true">Type</label>
            </settings>
        </column>
        <column name="sku" sortOrder="70">
            <settings>
                <filter>text</filter>
                <label translate="true">SKU</label>
            </settings>
        </column>
        <column name="price" class="Magento\Catalog\Ui\Component\Listing\Columns\Price" sortOrder="80">
            <settings>
                <addField>true</addField>
                <filter>textRange</filter>
                <label translate="true">Price</label>
            </settings>
        </column>
    </columns>
</listing>

Webkul/CustomLink/Ui/DataProvider/Product/CustomLinkDataProvider.php

Now, it’s time to create a data provider for our component.

<?php
namespace Webkul\CustomLink\Ui\DataProvider\Product;

use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Ui\DataProvider\Product\Related\AbstractDataProvider;

class CustomLinkDataProvider extends AbstractDataProvider
{
    /**
     * {@inheritdoc}
     */
    protected function getLinkType()
    {
        return 'customlink';
    }

    /**
     * {@inheritdoc}
     * @since 101.0.0
     */
    public function getCollection()
    {
        /** @var Collection $collection */
        $collection = parent::getCollection();
        $collection->addAttributeToSelect('status');

        if ($this->getStore()) {
            $collection->setStore($this->getStore());
        }

        if (!$this->getProduct()) {
            return $collection;
        }

        $collection->addAttributeToFilter(
            $collection->getIdFieldName(),
            ['nin' => [$this->getProduct()->getId()]]
        );

        $collection->setVisibility(
            $this->getVisibleInSiteIds()
        );

        return $this->addCollectionFilters($collection);
    }

    /**
     * Return visible site ids
     *
     * @return array
     */
    private function getVisibleInSiteIds()
    {
        return [
            Visibility::VISIBILITY_IN_SEARCH,
            Visibility::VISIBILITY_IN_CATALOG,
            Visibility::VISIBILITY_BOTH
        ];
    }
}

So now, you can see the custom product link section on the admin product edit page.

Custom Link Products Tab
Custom Link Section

This is how we can create Custom Linked Product Types in Magento 2 and if you still have any issues, feel free to add a ticket.

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home