Overview
Magento supports a number of product types, each with its own behavior and attributes. This powerful concept allows Magento to support a wide range of industries and merchant needs by mixing and matching product experiences in their catalog.
Even more powerful, however, is the ability for developers to easily add new product types.
In general, when a product class has distinct behavior or attributes, it should be represented by its own product type. This allows the product type to have complex and custom logic and presentation, with no impact on other product types — ensuring that native product types can continue to function as intended.
Implementation
Config XML
As with most things in Magento, the first step to defining a custom product type involves 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="custom_product_type_code" label="Custom Product Type Label" modelInstance="VendorModuleModelProductType"> </type> </config>
The type node has three required attributes.
- name: This attribute defines the custom product code which is used in code and in the database.
- label: Defines the product type label in the Magento admin.
- modelInstance: Specifies the fully qualified namespace of the corresponding product type model.
Although there are many other nodes and attributes that can influence the product type definition, this is an example of the simplest case.
Product Type Model
Having declared the product type, the next critical component is the product type model. Each product instance is associated with an instance of the corresponding product type model. This model has the opportunity to modify product type behavior and attributes and is called during many product manipulation processes.
Product Type Model Abstract
A product model must inherit from the MagentoCatalogModelProductTypeAbstractType base class. The base class has only one abstract method: deleteTypeSpecificData. This oddly specific method is called during a product instance save 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.
Finally, although not strictly required, it is conventional for product type models to include a constant with the name TYPE_CODE which defines the product type code string, so it can be referenced in code in a consistent manner.
<?php namespace VendorModuleModelProduct; class Type extends MagentoCatalogModelProductTypeAbstractType { const TYPE_ID = 'custom_product_type_code'; /** * {@inheritdoc} */ public function deleteTypeSpecificData(MagentoCatalogModelProduct $product) { // method intentionally empty } }
Core Product Types
It’s often the case that a custom product type behaves like and has similar requirements of a core product type. In this case, it can be convenient to extend one of the core product types instead of the abstract directly.
The following product type classes are available natively in the core.
- Simple: MagentoCatalogModelProductTypeSimple
- Virtual: MagentoCatalogModelProductTypeVirtual
- Configurable: MagentoConfigurableProductModelProductTypeConfigurable
- Grouped: MagentoGroupedProductModelProductTypeGrouped
- Downloadable: MagentoDownloadableModelProductType
- Bundle: MagentoBundleModelProductType
- Giftcard (Enterprise Edition Only): MagentoGiftCardModelCatalogProductTypeGiftcard
Results
Once the product type is declared in XML and its associated product type model is created, products of the new type can be created in the admin.
Additional Steps
Having successfully created a new product type, there are some common (although optional) steps to fully utilize it.
Associate With Common Attributes
Since product attributes can be scoped to relevant product types, any core attributes which are already scoped will not apply to the new product type. Any such attributes which are indeed relevant will need to be associated with the new product type in an install data script.
<?php namespace VendorModuleSetup; use MagentoEavSetupEavSetup; use MagentoEavSetupEavSetupFactory; use MagentoFrameworkSetupInstallDataInterface; use MagentoFrameworkSetupModuleContextInterface; use MagentoFrameworkSetupModuleDataSetupInterface; class InstallData implements InstallDataInterface { /** * EAV setup factory * * @var EavSetupFactory */ protected $eavSetupFactory; /** * Init * * @param EavSetupFactory $eavSetupFactory */ public function __construct(EavSetupFactory $eavSetupFactory) { $this->eavSetupFactory = $eavSetupFactory; } /** * {@inheritdoc} */ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); //associate these attributes with new product type $fieldList = [ 'price', 'special_price', 'special_from_date', 'special_to_date', 'minimal_price', 'cost', 'tier_price', 'weight', ]; // make these attributes applicable to new product type foreach ($fieldList as $field) { $applyTo = explode( ',', $eavSetup->getAttribute(MagentoCatalogModelProduct::ENTITY, $field, 'apply_to') ); if (!in_array(VendorModuleModelProductType::TYPE_ID, $applyTo)) { $applyTo[] = VendorModuleModelProductType::TYPE_ID; $eavSetup->updateAttribute( MagentoCatalogModelProduct::ENTITY, $field, 'apply_to', implode(',', $applyTo) ); } } } }
This example install script associates price and weight attributes in the $fieldList array with the new product type. Once this install script is run, these product attributes will show in the admin when editing or creating a product of the new type.
Composite Products
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="custom_product_type_code" label="Custom Product Type Label" modelInstance="VendorModuleModelProductType"> </type> <composableTypes> <type name="custom_product_type_code" /> </composableTypes> </config>
Where To Go From Here
Creating a new product type is only the beginning. Now that it’s defined, the new product type provides an enormous capacity for scoped customization.
Product Type Model Methods
The behavior of products can be significantly influenced by overriding methods in the product type model. This allows custom behaviors to be encapsulated within the product type rather than global observers/plugins.
Some examples of such methods:
- beforeSave() and save(): these methods run during the beforeSave() and afterSave(), respectively, methods of products of the given type.
- isSalable(): allows a custom product type to customize product saleability.
- _prepareProduct(): provides a hook to interact with the info by request when a product is initially added to the cart.
Price Model
In addition to the product type model, a module can specify a price model in the product type definition 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="custom_product_type_code" label="Custom Product Type Label" modelInstance="VendorModuleModelProductType"> <priceModel instance="VendorModuleModelProductPrice" /> </type> </config>
The price model referenced should extend MagentoCatalogModelProductTypePrice. This class has no abstract methods which must be implemented, but allows extending classes to interact with nearly every aspect of price calculation.
Summary
By providing a framework for defining custom product types, Magento empowers merchants and developers to implement significant customization to facilitate business requirements without risking default product behavior.