Creating a new product type in Magento 2 is actually relatively easy.

Yay!

As with most of these tutorials I make the assumption that you have a valid module registered with Magento. If this is not the case you should create one before you continue if you wish to try out the code.

You first you need to declare your new product type using the product_types.xml located in the etc folder of your module. In app/code/[codePool]/[Vendor]/GenericProductType/etc/product_types.xml

1
2
3
4
5
6
7
8
9
10
11
<?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="generic" label="Generic Product" modelInstance="[Vendor]\GenericProductType\Model\Product\Type\Generic" indexPriority="100" sortOrder="10">
        <customAttributes>
            <attribute name="refundable" value="true"/>
        </customAttributes>
    </type>
    <composableTypes>
        <type name="generic" />
    </composableTypes>
</config>

If you now try creating a new product, you will be able to select “Generic Product” from the drop down.

You will though get the following error;

Class [Vendor]\GenericProductType\Model\Product\Type\Generic does not exist

That is because we have not created our module we have declared with the modelInstance attribute in our product_types.xml

Let’s do that now.

In app/code/[codePool]/[Vendor]/GenericProductType/Model/Product/Type/Generic.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

namespace [Vendor]\GenericProductType\Model\Product\Type;

use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Type\AbstractType;

class Generic extends AbstractType
{
    const TYPE_CODE = 'generic';

    public function deleteTypeSpecificData(Product $product)
    {
    }
}

At some point in the inheritance chain we must extend the AbstractType class.

It is also good practice to declare a TYPE_CODE constant that makes it easy later on to do product type checking. You will see this later on.

The AbstractType class declares an abstract function deleteTypeSpecificData(Product $product) that we must implement even if our implementation does nothing with it.

Now try creating the product again. There should be no error thrown.

Hang on, yes i can create a product but i cannot add any prices!

Why?

Price attributes, among others, are set to apply to certain product types. We need to tell Magento that prices should also apply to our generic product type.

We do this in a setup script.

In app/code/[codePool]/[Vendor]/GenericProductType/Setup/InstallData.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

namespace [Vendor]\GenericProductType\Setup;

use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use [Vendor]\GenericProductType\Model\Product\Type\Generic;

class InstallData implements InstallDataInterface
{
    protected $eavSetupFactory;

    public function __construct(EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        /** @var \Magento\Eav\Setup\EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        // price attributes we want to apply
        // to our product type
        $attributes = [
            'price',
            'special_price',
            'special_from_date',
            'special_to_date',
            'minimal_price',
            'tax_class_id'
        ];

        foreach ($attributes as $attributeCode) {
            $relatedProductTypes = explode(
                ',',
                $eavSetup->getAttribute(Product::ENTITY, $attributeCode, 'apply_to')
            );
            if (!in_array(Generic::TYPE_CODE, $relatedProductTypes)) {
                $relatedProductTypes[] = Generic::TYPE_CODE;
                $eavSetup->updateAttribute(
                    Product::ENTITY,
                    $attributeCode,
                    'apply_to',
                    join(',', $relatedProductTypes)
                );
            }
        }
    }
}

Woah!

What are we doing?

Ok firstly to add attributes to products we need to use the eavSetupFactory. We inject that in to our constructor as a dependency and then we instantiate it (line 24)

Our attributes array (line 28) is just a list of attributes we want to apply our generic type to.

foreach of our attributes we first get any product types that are already applied (line 38 - 41). We do this because we want to append our own and not just blow away any data that already exists. If our product type does not exist on this price attribute (line 42) then we add it (line 43) and re-save the attribute.

Note how we check if our product type is already in the list (if (!in_array(Generic::TYPE_CODE, $relatedProductTypes)) {). This is why we added a TYPE_CODE to our class to make it easier to do checks like this.

If we now refresh our admin screen the price fields should appear.

Woop woop.

Ok so our new product type doesn’t do much more than a simple product in it’s current state. But you could use it to modify templates on the product view page for example.

Every product page gets a product type handle in the format catalog_product_view_type_[type].xml so in the above case you could create a catalog_product_view_type_generic.xml to do generic product type specfic things.

Code is available on github

Don’t forget, if you are copying and pasting the code samples from above, you need to replace [Vendor] with your own namespace (something unique to you).

  • magento2

Like this post? Share it :)


Related Posts

Back