Skip to content
intermediate Phase 64 · Extension Strategy

Extension Attributes — Custom Attributes for Existing Entities

Adding custom attributes to existing Magento 2 entities using extension attribute interfaces and implementing extension attribute storage

45m
1 problems
Topic Progress 0%

Extension Attributes Interface

Defining Extension Attributes

Create an interface extending the entity's extension attributes interface:

namespace Vendor\Module\Api\Data;

use Magento\Catalog\Api\Data\ProductExtensionInterface;

interface ProductExtensionInterface extends ProductExtensionInterface
{
    const CUSTOM_FIELD = 'custom_field';
    const CUSTOM_SELECT = 'custom_select';
    
    public function getCustomField(): ?string;
    public function setCustomField(?string $value): self;
    public function getCustomSelect(): ?string;
    public function setCustomSelect(?string $value): self;
}

di.xml Configuration

Map the interface to your implementation:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Vendor\Module\Api\Data\ProductExtensionInterface"
                type="Vendor\Module\Model\Data\ProductExtension"/>
</config>

Implementation

namespace Vendor\Module\Model\Data;

use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Vendor\Module\Api\Data\ProductExtensionInterface;

class ProductExtension implements ProductExtensionInterface
{
    private ?string $customField = null;
    private ?string $customSelect = null;
    
    public function getCustomField(): ?string {
        return $this->customField;
    }
    public function setCustomField(?string $value): self {
        $this->customField = $value;
        return $this;
    }
    public function getCustomSelect(): ?string {
        return $this->customSelect;
    }
    public function setCustomSelect(?string $value): self {
        $this->customSelect = $value;
        return $this;
    }
}

Storing Extension Attributes

Database Table

Create a table for your extension attributes:

<!-- db_schema.xml -->
<table name="vendor_module_product_extension" resource="default" engine="innodb">
    <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true"/>
    <column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="false"/>
    <column xsi:type="varchar" name="custom_field" nullable="true" length="255"/>
    <column xsi:type="varchar" name="custom_select" nullable="true" length="50"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
        <column name="id"/>
    </constraint>
    <constraint xsi:type="unique" referenceId="VENDOR_MODULE_PRODUCT_EXTENSION_PRODUCT_ID">
        <column name="product_id"/>
    </constraint>
    <constraint xsi:type="foreign" referenceId="FK_PRODUCT_ID">
        <column name="product_id"/>
        <referenceTable name="catalog_product_entity" referenceColumn="entity_id"/>
    </constraint>
</table>

Plugin to Load Extension Attributes

namespace Vendor\Module\Plugin;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Vendor\Module\Api\Data\ProductExtensionInterfaceFactory;
use Vendor\Module\Model\ResourceModel\ProductExtension as ExtensionResource;

class ProductRepositoryPlugin
{
    public function __construct(
        private ProductExtensionInterfaceFactory $extensionFactory,
        private ExtensionResource $extensionResource
    ) {}

    public function afterGet(
        ProductRepositoryInterface $subject,
        \Magento\Catalog\Model\Product $product
    ) {
        $extension = $product->getExtensionAttributes();
        if (!$extension) {
            $extension = $this->extensionFactory->create();
        }
        $data = $this->extensionResource->load($product->getId());
        $extension->setCustomField($data['custom_field'] ?? null);
        $extension->setCustomSelect($data['custom_select'] ?? null);
        $product->setExtensionAttributes($extension);
        return $product;
    }
}

Saving Extension Attributes

Plugin to Save Extension Attributes

namespace Vendor\Module\Plugin;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Vendor\Module\Model\ResourceModel\ProductExtension as ExtensionResource;

class ProductRepositorySavePlugin
{
    public function __construct(
        private ExtensionResource $extensionResource
    ) {}

    public function beforeSave(
        ProductRepositoryInterface $subject,
        \Magento\Catalog\Model\Product $product,
        bool $saveOptions = false
    ) {
        $extension = $product->getExtensionAttributes();
        if ($extension) {
            $this->extensionResource->save($product->getId(), [
                'custom_field' => $extension->getCustomField(),
                'custom_select' => $extension->getCustomSelect()
            ]);
        }
    }
}

REST API Usage

// GET /rest/V1/products/SKU
{
    "sku": "SKU",
    "name": "Product Name",
    "extension_attributes": {
        "custom_field": "value",
        "custom_select": "option1"
    }
}

// PUT /rest/V1/products/SKU
{
    "product": {
        "sku": "SKU",
        "extension_attributes": {
            "custom_field": "new value"
        }
    }
}

GraphQL Usage

query {
  products(filter: { sku: { eq: "SKU" } }) {
    items {
      sku
      name
      custom_field
    }
  }
}

Extension Attributes Best Practices

Naming Convention

// Prefix with your vendor/module
interface ProductExtensionInterface {
    const VENDOR_FIELD = 'vendor_field';
    
    public function getVendorField(): ?string;
    public function setVendorField(?string $value): self;
}

Null Safety

Always handle null values:

$extension = $product->getExtensionAttributes();
if ($extension && $extension->getCustomField() !== null) {
    // Process value
}

Entity Support

Extension attributes work for these entities:

Entity Interface
Product ProductExtensionInterface
Category CategoryExtensionInterface
Order OrderExtensionInterface
Invoice InvoiceExtensionInterface
Shipment ShipmentExtensionInterface
Customer CustomerExtensionInterface
Address AddressExtensionInterface

Performance Considerations

  • Load extension attributes only when needed (lazy loading)
  • Use db_schema.xml for declarative schema
  • Index frequently queried extension attribute columns
  • Avoid N+1 queries when listing entities with extension attributes

Practice Problems

0 / 1 solved
Extension Attribute Not Saving

Extension attributes are set via API but not persisted to database. Diagnose the issue.

Quiz

1. What interface must you extend for product extension attributes?

Question 1 options

2. How are extension attributes exposed in REST API?

Question 2 options

3. How do you persist extension attributes to the database?

Question 3 options

4. Which file defines the database schema for extension attributes?

Question 4 options

Flashcards

Question

What is the extension attributes pattern?

Answer

Extending entity interfaces to add custom fields accessible via API and stored in custom tables

Question

How to persist extension attributes?

Answer

Plugins on repository save/load methods with a custom resource model

Question

Which file defines custom table schema?

Answer

db_schema.xml using declarative schema

Question

How are extension attributes accessed?

Answer

$entity->getExtensionAttributes()->getCustomField()

Question

Which entities support extension attributes?

Answer

Product, Category, Order, Invoice, Shipment, Customer, Address

Revision Notes

Key Takeaways

  • 1. Extension attributes extend entity interfaces with custom fields
  • 2. Use db_schema.xml for declarative schema of custom tables
  • 3. Plugins on repository save/load methods handle persistence
  • 4. Extension attributes appear in extension_attributes in API responses
  • 5. Always handle null values and prefix constants with vendor/module
  • 6. Lazy load extension attributes for performance

Interview Tips

  • Explain the extension attributes pattern and its benefits over EAV customization
  • Describe how extension attributes are persisted and loaded
  • Discuss performance considerations for extension attributes
  • Give examples of entities that support extension attributes

Cheat Sheet

Extension Attributes Cheat Sheet

1. Create interface:

interface ProductExtensionInterface {
    public function getCustomField(): ?string;
    public function setCustomField(?string $value): self;
}

2. Map in di.xml:

<preference for="...Interface" type="...Implementation"/>

3. Create table:
db_schema.xml

4. Plugin on repository:
afterGet: load from DB
beforeSave: save to DB

5. Access:
$product->getExtensionAttributes()->getCustomField()