Magento 2

Magento fundamentals: what are the differences between custom and extension attributes?

5 minutes reading

Introduction

The purpose of this article is to shed some light on the flexible yet not-so-intuitive entity attribute system offered by Magento 2.

There are plenty of resources about implementing custom and extension attributes, so here we will just focus on the main differences and some details.

I would recommend referring to the official Magento documentation or, even better, watching mage2.tv for a comprehensive hands-on guide.

Quick overview of the entity attribute system

Since the early days, Magento has offered an extensible entity attribute system based on the Entity-Attribute-Value (EAV) model.

Through this system, it’s straightforward to extend a Magento entity, with some limits:

  • only EAV entities can be extended with attributes; not all entities in a default Magento installation are EAV entities, just products, categories, customers, and customer addresses.
  • attributes can only contain scalar values, that is, boolean, int, float, or string values.

The extension attributes system, introduced in Magento 2, overcomes the above limits, making it easy to:

  • extend non-EAV entities to a condition: the entity should implement the \Magento\Framework\Api\ExtensibleDataInterface;
  • use objects to have more complex types.

Unluckily, some Magento core entities don’t implement the \Magento\Framework\Api\ExtensibleDataInterface thus they are not extensible with native extension attributes. Anyway, let’s focus on the half-full glass to explore some advantages of the extension attributes system.

What are custom attributes?

With the introduction of the extension attributes system, some new terminology comes in.

EAV attributes belong to one of the following sets:

  • system attributes - created by any default Magento installation;
  • custom attributes - created in the Admin Panel or through data patches.

So, simply put, a custom attribute is an EAV attribute created by someone else.

What are extension attributes?

We have already given a definition but let’s refresh it: an extension attribute is an attribute that extends a non-EAV entity.

But obviously, there is more.

First, the good news: an extension attribute allows us to overcome the limitations of scalar values. With an extension attribute, we can extend both EAV and non-EAV with complex objects.

For example, the gift_message extension attribute added to the order (Magento\Sales\Api\Data\OrderInterface) is of the following type:

<?php
namespace Magento\GiftMessage\Api\Data;

interface MessageInterface 
  extends \Magento\Framework\Api\ExtensibleDataInterface
{
    /**#@+
     * Constants for keys of data array. Identical to the name of the getter in snake case
     */
    const GIFT_MESSAGE_ID = 'gift_message_id';
    const CUSTOMER_ID = 'customer_id';
    const SENDER = 'sender';
    const RECIPIENT = 'recipient';
    const MESSAGE = 'message';
    /**#@-*/

    // ...
}

The price we pay for such flexibility is that extension attribute values persistency needs to be populated programmatically, whereas custom attribute values are loaded and saved automatically.

Refer to the official documentation for more details on how to add extension attributes to entity.

Let’s note that ExtensibleDataInterface doesn’t declare the methods to access extension attributes. Here is its declaration:

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Framework\Api;

/**
 * Interface for entities which can be extended with extension attributes.
 *
 * @api
 */
interface ExtensibleDataInterface
{
    /**
     * Key for extension attributes object
     */
    const EXTENSION_ATTRIBUTES_KEY = 'extension_attributes';
}

By convention, the methods are named setExtensionAttributes() and getExtensionAttributes() but we can’t give for granted that a class implementing ExtensibleDataInterface provides those methods, so my advice is to double check it.

It’s worth mentioning that to prevent performance degradation while fetching extension attributes in collections, Magento provides a native join functionality (documented here), but this feature is limited to scalar extension attributes.

To find an example, we can look at the declaration of the is_subscribed attribute added to a customer in the module-newsletter/etc/extension_attributes.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface">
        <attribute code="is_subscribed" type="boolean" >
            <join reference_table="newsletter_subscriber" 
                  reference_field="customer_id" 
                  join_on_field="entity_id">
                <field>subscriber_status</field>
            </join>
        </attribute>
    </extension_attributes>
</config>

💡 Let’s pay attention: for the join to work, it’s not enough to declare the above join; we also have to pass the collection to the \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::process() method to have it populated with correct data. We can see an example in the getList() method of \Magento\Catalog\Model\ProductRepository:

<?php
namespace Magento\Catalog\Model;

// ...

class ProductRepository 
  implements \Magento\Catalog\Api\ProductRepositoryInterface
{
    // ...

    public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
    {
        /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
        $collection = $this->collectionFactory->create();
        $this->extensionAttributesJoinProcessor->process($collection);
        // ..
    }

    // ...
}

Another relevant feature is that we can restrict access to the values of an entity’s extension attribute by declaring an ACL resource, as shown in the official documentation.

Where to store extension attributes?

Usually, extension attributes are non-EAV attributes, meaning that we should decide where to persist their values.

If the extension attribute is a complex object, the default choice would be to use a separate custom table.

But if the extension attribute is a scalar, extending core tables with a non-ambiguous custom column is acceptable. This way, persisting values will require less code and will be more performant because it avoids additional join conditions.

There isn’t a golden rule, though; we need to properly analyze every case before making the right decision.

Conclusions

Custom attributes are nothing new in the Magento world: just a new name given to EAV-entity attributes that Magento installation does not create.

Extension attributes, instead, are a new way to extend non-EAV entities.

With extension attributes also comes the possibility to define complex attributes, overcoming the limit of scalar types.

All this comes at a bit of cost: we have to carefully handle persistence to ensure that data is retrieved and saved the way we expect.

Post of

COO | Reggio Emilia

Alessandro works at Bitbull as an experienced technical leader devoted to software design, development, and mentoring.
Honored three times with the title of Magento Master and listed among the top 50 contributors in the last years, he is also an active Magento Community Maintainer since 2018 and member of the Magento Association content committee since 2020.