Menu Close
    Searching for an experienced Magento 2 Development Company ?

    Custom Linked Product Types Magento2

    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.

    <?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.

    . . .
    Discuss on Helpdesk

    Leave a Comment

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


    Be the first to comment.

    Back to Top