Edit in GitHubLog an issue

Add extension attributes to entities

Third-party developers cannot change the API data interfaces defined in the Adobe Commerce and Magento Open Source code. However, most of these entities have a feature called extension attributes. Check the interface for the methods getExtensionAttributes() and setExtensionAttributes() to determine if they are available for the entity.

In order to retrieve a product or a list of products from the API, you need to make an API request to the appropriate service (the Product Repository in this case). The response to these requests will return objects with the following structure:

Product response#

Copied to your clipboard
1<product>
2 <id>1</id>
3 <sku>some-sku</sku>
4 <custom_attributes><!-- Custom Attributes Data --></custom_attributes>
5 <extension_attributes><!-- Here should we add extension attributes data --></extension_attributes>
6</product>

Product list response:#

Copied to your clipboard
1<products>
2 <item>
3 <id>1</id>
4 <sku>some-sku</sku>
5 <custom_attributes><!-- Custom Attributes Data --></custom_attributes>
6 <extension_attributes><!-- Here should we add extension attributes data --></extension_attributes>
7 </item>
8 <item>
9 <id>2</id>
10 <sku>some-sku-2</sku>
11 <custom_attributes><!-- Custom Attributes Data --></custom_attributes>
12 <extension_attributes><!-- Here should we add extension attributes data --></extension_attributes>
13 </item>
14</products>

Add plugin to product repository#

In order to add extension attributes, we need to use an after plugin on Product Repository. The plugin should be declared for the methods: save, get and getList.

We can add scalar and non-scalar extension attributes. Scalar is a simple attribute. Non-scalar attributes can be represented by Data Object.

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7use Magento\Catalog\Api\ProductRepositoryInterface;
8use Magento\Catalog\Api\Data\ProductInterface;
9
10public function afterGet
11(
12 ProductRepositoryInterface $subject,
13 ProductInterface $entity
14) {
15 $ourCustomData = $this->customDataRepository->get($entity->getId());
16
17 $extensionAttributes = $entity->getExtensionAttributes(); /** get current extension attributes from entity **/
18 $extensionAttributes->setOurCustomData($ourCustomData);
19 $entity->setExtensionAttributes($extensionAttributes);
20
21 return $entity;
22}

This is the simplest way to add extension attributes without causing a conflict:

  • We get the entity's extension attributes, if they are already set.
  • We add our extension attribute.
  • Finally set the extension attribute on the entity with ours included.

Function afterGetList is similar to afterGet:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7use Magento\Catalog\Api\ProductRepositoryInterface;
8use Magento\Catalog\Api\Data\ProductSearchResultsInterface;
9
10public function afterGetList(
11 ProductRepositoryInterface $subject,
12 ProductSearchResultsInterface $searchResults
13) : ProductSearchResultsInterface {
14 $products = [];
15 foreach ($searchResults->getItems() as $entity) {
16 $ourCustomData = $this->customDataRepository->get($entity->getId());
17
18 $extensionAttributes = $entity->getExtensionAttributes();
19 $extensionAttributes->setOurCustomData($ourCustomData);
20 $entity->setExtensionAttributes($extensionAttributes);
21
22 $products[] = $entity;
23 }
24 $searchResults->setItems($products);
25 return $searchResults;
26}

Likewise, the afterSave plugin should manipulate the entity data before returning it:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7use Magento\Catalog\Api\Data\ProductInterface;
8use Magento\Catalog\Api\ProductRepositoryInterface;
9
10public function afterSave
11(
12 ProductRepositoryInterface $subject,
13 ProductInterface $result, /** result from the save call **/
14 ProductInterface $entity /** original parameter to the call **/
15 /** other parameter not required **/
16) {
17 $extensionAttributes = $entity->getExtensionAttributes(); /** get original extension attributes from entity **/
18 $ourCustomData = $extensionAttributes->getOurCustomData();
19 $this->customDataRepository->save($ourCustomData);
20
21 $resultAttributes = $result->getExtensionAttributes(); /** get extension attributes as they exist after save **/
22 $resultAttributes->setOurCustomData($ourCustomData); /** update the extension attributes with correct data **/
23 $result->setExtensionAttributes($resultAttributes);
24
25 return $result;
26}

But if some entity doesn't have implementation to fetch extension attributes, we will always retrieve null and each time when we fetch extension attributes we need to check if they are null. If so, then we need to create them. To avoid such code duplication, we need to create afterGetExtensionAttributes plugin for our entity with extension attributes.

Let's assume the product entity doesn't have any implementation of extension attributes, so our plugin might look like this:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7use Magento\Catalog\Api\Data\ProductExtensionInterface;
8use Magento\Catalog\Api\Data\ProductInterface;
9use Magento\Catalog\Api\Data\ProductExtensionFactory;
10
11class ProductAttributesLoad
12{
13 /**
14 * @var ProductExtensionFactory
15 */
16 private $extensionFactory;
17
18 /**
19 * @param ProductExtensionFactory $extensionFactory
20 */
21 public function __construct(ProductExtensionFactory $extensionFactory)
22 {
23 $this->extensionFactory = $extensionFactory;
24 }
25
26 /**
27 * Loads product entity extension attributes
28 *
29 * @param ProductInterface $entity
30 * @param ProductExtensionInterface|null $extension
31 * @return ProductExtensionInterface
32 */
33 public function afterGetExtensionAttributes(
34 ProductInterface $entity,
35 ProductExtensionInterface $extension = null
36 ) {
37 if ($extension === null) {
38 $extension = $this->extensionFactory->create();
39 }
40
41 return $extension;
42 }
43}
44

Now we need to bind our plugin to ProductInterface:

Copied to your clipboard
1<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
2 <type name="Magento\Catalog\Api\Data\ProductInterface">
3 <plugin name="ProductExtensionAttributeOperations" type="Magento\Catalog\Plugin\ProductAttributesLoad"/>
4 </type>
5</config>

Configure extension attributes#

The file that holds these extension attributes must reside under the /etc folder of your module.

For scalar attributes, we can use the following configuration:

Copied to your clipboard
1<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
2 <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
3 <attribute code="first_custom_attribute" type="int" />
4 <attribute code="second_custom_attribute" type="string" />
5 </extension_attributes>
6</config>

Here, the scalar attributes indicate the simple form of attribute representation, such as an integer or a string. Specify the class or interface of the extension attributes inside the "for" attribute of the <extension_attributes> tag. In this case, it is the ProductInterface. The attribute is specified with a unique code and its type.

For non-scalar attributes:

Copied to your clipboard
1<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
2 <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
3 <attribute code="our_custom_data" type="Magento\SomeModule\Api\Data\CustomDataInterface" />
4 </extension_attributes>
5</config>

Here, the non-scalar attributes indicate data objects such as the instance of a class. In the above example, the CustomDataInterface object is added as an extension attribute.

For array extension attributes:

Copied to your clipboard
1<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
2 <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
3 <attribute code="some_custom_data" type="string[]" />
4 </extension_attributes>
5</config>

The array extension attributes are just an extension of the scalar attributes where a range of values can be represented as an attribute. The [] symbol indicates the attribute type is an array.

The array indicator [] can also be appended to non-scalar types.

In first - scalar - case we will get the next result:

Copied to your clipboard
1<product>
2 <id>1</id>
3 <sku>some-sku</sku>
4 <custom_attributes><!-- Custom Attributes Data --></custom_attributes>
5 <extension_attributes>
6 <first_custom_attribute>1</first_custom_attribute>
7 <second_custom_attribute>foo</second_custom_attribute>
8 </extension_attributes>
9</product>

In second, non-scalar one:

Copied to your clipboard
1<product>
2 <id>1</id>
3 <sku>some-sku</sku>
4 <custom_attributes><!-- Custom Attributes Data --></custom_attributes>
5 <extension_attributes>
6 <our_custom_data>
7 <!-- fields defined in CustomDataInterface are here -->
8 </our_custom_data>
9 </extension_attributes>
10</product>

In third, array one (in JSON for a change):

Copied to your clipboard
1{
2 "id": 1,
3 "sku": "some-sku",
4 "custom_attributes": { /* ... custom attribute data ... */ },
5 "extension_attributes": {
6 "some_custom_data": ["value1", "value2", "value3"]
7 }
8}
Was this helpful?
  • Privacy
  • Terms of Use
  • Do not sell my personal information
  • AdChoices
Copyright © 2022 Adobe. All rights reserved.