As you know, Magento supports several product types, each type has different behavior and logic. As the default, there are six types of products you can set: Simple Product, Grouped Product, Configurable Product, Bundle Product, Virtual Product, and Downloadable Product.

But in some cases, the default Magento product types cannot be enough for your business. So depends on your situation, you can create a new Magento 2 product type with the following tutorial.

Execution

1. Config XML

The first step is defining a custom product type with an XML declaration. Specifically, in the <Vendor>/<Module>/etc/product_types.xml, add an XML snippet of the following form to declare the new product type’s critical information.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
    <type name="new_product_type_code" label="New Product Type" modelInstance="Vendor\Module\Model\Product\Type\NewProductType">
    </type>
</config>

There are attributes for product type definition:

- name: Custom product attribute code used in code and database.

- label: Defines the product type label in Magento admin.

- modelInstance: Custom product type model specifies custom business logic.

- canUseQtyDecimals: Specify this product type can use decimal qty

Besides the type definition, we have the following tag that can be associated with product type, you can use it for custom your product behavior:

  • indexerModel: Using custom indexer model for indexing process.
  • priceModel: Using a custom price model for calculating the price.
  • stockIndexerModel: Using a custom model for the stock indexing process.
  • customAttributes: Define the custom attribute for the new product type

2. Product Type Model

A product model must inherit from the \Magento\Catalog\Model\Product\Type\AbstractType base class. You can override any method for custom logic code.

Method: deleteTypeSpecificData is an abstract method.

This specific method is called while saving a product instance if its type has changed, and gives the original product type the opportunity to clean up any type-specific data before the type change is finalized.

Unless the new custom product type has such type-specific data, the method can be overridden with an empty method. After this, it is possible to rewrite some functions and implement some changes you want there.

<?php
namespace Vendor\Module\Model\Product\Type;

class NewProductType extends \Magento\Catalog\Model\Product\Type\AbstractType
{
    const TYPE_CODE= 'new_product_type_code';

    public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product)
    {

    }
}

Some common methods you can override are:

- isVirtual: Check is virtual product.

- isSaleable: Check is product available for sale.

- getWeight: Default action to get the weight of the product.

- hasOptions: Return true of product has options.

- hasRequiredOptions: Check if the product has any required options.

- getAssociatedProducts: Get the list of products related to product

After all of the above steps, when you check the Add new product dropdown, here is what you should see:

3. Price Model

You can specify a price model in product type Config XML.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
    <type name="new_product_type_code" label="New Product Type" modelInstance="Vendor\Module\Model\Product\Type\NewProductType">
        <priceModel instance="Vendor\Module\Product\Type\Price" />
    </type>
</config>

The price model referenced should extend \Magento\Catalog\Model\Product\Type\Price

This class has no abstract methods which must be implemented but allows extending classes to interact with nearly every aspect of price calculation.

Here you can define your pricing logic like you can add a certain amount always of the actual cost to a product or some other logic.

<?php
namespace Vendor\Module\Model\Product\Type;

class Price extends \Magento\Catalog\Model\Product\Type\Price
{   
  // your custom code
}

4. Add attributes for the new product type.

Some product attribute only applies to a few default Magento product types, so you need to add your new product type into them.

<?php

namespace Vendor\Module\Setup\Patch\Data;

use Vendor\Module\Model\Product\Type\<;
use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class NewProductTypeAttributes implements DataPatchInterface
{
    /**
     * @var ModuleDataSetupInterface
     */
    private $setup;

    /**
     * EAV setup factory
     *
     * @var EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * Init
     *
     * @param ModuleDataSetupInterface $setup
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        ModuleDataSetupInterface $setup,
        EavSetupFactory $eavSetupFactory
    ) {
        $this->setup = $setup;
        $this->eavSetupFactory = $eavSetupFactory;
    }

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

        $fieldList = [
            'price',
            'special_price',
            'special_from_date',
            'special_to_date',
            'minimal_price',
            'cost',
            'tier_price',
            'weight',
        ];

        foreach ($fieldList as $field) {
            $applyTo = explode(
                ',',
                $eavSetup->getAttribute(Product::ENTITY, $field, 'apply_to')
            );
            if (!in_array(NewProductType::TYPE_CODE, $applyTo)) {
                $applyTo[] = NewProductType::TYPE_CODE;
                $eavSetup->updateAttribute(
                    Product::ENTITY,
                    $field,
                    'apply_to',
                    implode(',', $applyTo)
                );
            }
        }
    }

    /**
     * @return string[]|void
     */
    public static function getDependencies()
    {
        return [];
    }

    /**
     * @return string[]|void
     */
    public function getAliases()
    {
        return [];
    }
}

Core Product Types

You can extend one of the core product types instead of the abstract directly.

The following product type classes are available natively in the core.

Simple: \Magento\Catalog\Model\Product\Type\Simple

Virtual: \Magento\Catalog\Model\Product\Type\Virtual

Configurable: \Magento\ConfigurableProduct\Model\Product\Type\Configurable

Grouped: \Magento\GroupedProduct\Model\Product\Type\Grouped

Downloadable: \Magento\Downloadable\Model\Product\Type

Bundle: \Magento\Bundle\Model\Product\TypeGiftcard (Enterprise Edition Only): \Magento\GiftCard\Model\Catalog\Product\Type\Giftcard

Composite Products (Optional)

When adding child products to a composite product type (grouped, configurable, or bundle), the admin interface will only show products where the associated product type has explicitly declared its eligibility to be the child of a composite product. New product types can allow themselves to be children of composite product types by adding a node to the composableTypes node in Vendor/Module/etc/product_types.xml.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
    <type name="new_product_type_code" label="New Product Type" modelInstance="Vendor\Module\Model\Product\Type\NewProductType">
        <priceModel instance="Vendor\Module\Product\Type\Price" />
    </type>
    <composableTypes>
        <type name="new_product_type_code" />
    </composableTypes>
</config>

Bottom line

When all of the above is done, your directory structure looks like this.

Magento 2 provides a framework for creating custom product types. This is very helpful for merchants who want to create custom logic based on different products and services that they are selling.