Extending Magento 2 REST APIs with Service Contracts and Extension Attributes
Add custom catalog data to Magento 2 REST APIs safely using Service Contracts and Extension Attributes – a step‑by‑step guide.
15 Apr 2026, 05:13 UTC

When building integrations for Magento 2, adding custom catalog data to the public REST API is a frequent requirement. Directly overriding core models or adding custom controllers is risky: it breaks upgrade paths and can lead to serialization errors. The recommended approach is to extend Magento’s Service Contracts – a stable, interface‑driven API layer – and to expose new fields through Extension Attributes.
Prerequisites
- Magento 2.4.x in developer mode.
- Shell access with
sudoor a user that can runbin/magentocommands. - A skeleton module (e.g.,
Vendor/CustomCatalog) already registered inapp/code.
1. Define the Extension Attribute
Extension attributes allow you to attach extra data to existing entities without touching the core schema. Create app/code/Vendor/CustomCatalog/etc/extension_attributes.xml with the following content:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="Magento\\Catalog\\Api\\Data\\ProductInterface">
<attribute code="loyalty_points" type="Vendor\\CustomCatalog\\Api\\Data\\LoyaltyPointsInterface"/>
</extension_attributes>
</config>
Magento will now merge the loyalty_points attribute into the ProductInterface payloads.
2. Create a Data Transfer Object (DTO)
Service Contracts rely on interfaces to serialize and deserialize JSON. Define a DTO that describes the custom data:
namespace Vendor\\CustomCatalog\\Api\\Data;
interface LoyaltyPointsInterface
{
/**
* @return int
*/
public function getPoints();
/**
* @param int $points
* @return $this
*/
public function setPoints($points);
}
3. Implement the Repository Logic
Next, create a repository interface that will be exposed via the API. The interface should live in app/code/Vendor/CustomCatalog/Api/ProductRepositoryInterface.php:
namespace Vendor\\CustomCatalog\\Api;
interface ProductRepositoryInterface
{
/**
* @param string $sku
* @return \\Magento\\Catalog\\Api\\Data\\ProductInterface
*/
public function getBySkuWithPoints(string $sku);
}
Implement this interface in app/code/Vendor/CustomCatalog/Model/ProductRepository.php. The implementation loads the product, attaches the custom data, and returns the enriched product object.
namespace Vendor\\CustomCatalog\\Model;
use Magento\\Catalog\\Api\\Data\\ProductInterface;
use Magento\\Catalog\\Repository\\ProductRepositoryInterface as CoreProductRepository;
use Vendor\\CustomCatalog\\Api\\ProductRepositoryInterface;
use Vendor\\CustomCatalog\\Api\\Data\\LoyaltyPointsInterface;
class ProductRepository implements ProductRepositoryInterface
{
private CoreProductRepository $coreRepo;
private LoyaltyPointsInterface $pointsFactory;
public function __construct(
CoreProductRepository $coreRepo,
LoyaltyPointsInterface $pointsFactory
) {
$this->coreRepo = $coreRepo;
$this->pointsFactory = $pointsFactory;
}
public function getBySkuWithPoints(string $sku): ProductInterface
{
$product = $this->coreRepo->get($sku);
// Example: fetch points from a custom table or business rule
$points = $this->calculatePoints($product);
$ext = $product->getExtensionAttributes();
if (!$ext) {
$ext = $this->pointsFactory->create();
}
$ext->setPoints($points);
$product->setExtensionAttributes($ext);
return $product;
}
private function calculatePoints(ProductInterface $product): int
{
// Placeholder logic – replace with real calculation
return (int)round($product->getPrice() * 0.1);
}
}
4. Map the Route in webapi.xml
The webapi.xml file declares the REST endpoint, HTTP verb, ACL resource, and the service contract method. Create app/code/Vendor/CustomCatalog/etc/webapi.xml:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Webapi/etc/webapi.xsd">
<router id="rest" frontName="V1">
<route url="catalog/product/with-points/{sku}" method="GET">
<service class="Vendor\\CustomCatalog\\Api\\ProductRepositoryInterface" method="getBySkuWithPoints"/>
<resources>
<resource ref="Magento_Catalog::catalog_product_view"/>
</resources>
</route>
</router>
</config>
This maps a GET request to /V1/catalog/product/with-points/{sku} to the getBySkuWithPoints method. The ACL resource Magento_Catalog::catalog_product_view ensures only authorized callers can use the endpoint.
5. Register the Service Contract
Magento automatically registers interfaces in the Api namespace as service contracts. No additional configuration is needed beyond the webapi.xml mapping.
6. Deploy and Test
Run the following commands from the Magento root:
# Upgrade the schema and data
bin/magento setup:upgrade
# Re‑compile the dependency injection configuration
bin/magento setup:di:compile
# Clear caches
bin/magento cache:clean
Verify that the endpoint is registered:
bin/magento setup:api:info | grep "catalog/product/with-points"
Now issue a request. Replace {sku} with a real product SKU. Example using cURL:
curl -X GET \
"http://localhost/rest/V1/catalog/product/with-points/ABC123" \
-H "Authorization: Bearer "
The JSON response should include the extension_attributes.loyalty_points field:
{
"entity_id": 1,
"sku": "ABC123",
"name": "Sample Product",
"price": 99.99,
"extension_attributes": {
"loyalty_points": 10
}
}
7. Verify Data Integrity
- Check that the
loyalty_pointsvalue matches the calculation incalculatePoints(). - Inspect the generated
var/generation/*/Magento_Catalog/Model/Repository/ProductRepositoryInterface.phpfile to confirm the extension attribute is merged into the metadata. - Run
bin/magento dev:static-content:deployif you have custom UI components that consume the API.
8. Performance Considerations
Extension attributes are loaded lazily. However, if you request a product collection that includes the custom attribute, Magento will issue an additional query per product. Avoid using the attribute in large collection loads or implement a custom collection join if performance is critical.
9. Recovery and Rollback
Because this guide only adds new files and does not modify core code, a rollback is as simple as removing the module directory and running bin/magento module:disable Vendor_CustomCatalog followed by bin/magento setup:upgrade. No database changes were made in this example, so no data migration rollback is required.
Takeaway
By leveraging Service Contracts and Extension Attributes, you can expose custom catalog data through Magento 2 REST APIs in a way that is upgrade‑safe, testable, and aligned with Magento’s architectural principles.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.