Install the Products Backend API

Edit on GitHub

This document describes how to install the Products Backend API, which exposes concrete and abstract product data at /products on the Glue Backend API.

Prerequisites

Install the required features:

NAME VERSION INSTALLATION GUIDE
Spryker Core 202608.0 Install the Spryker Core feature
Self-Service Portal 202608.0 Install Self-Service Portal

The Self-Service Portal feature is optional. Install it only to use the productClass and shipmentType attributes.

Step 1 installs the remaining modules the API depends on, so you do not have to install them beforehand.

The API resource is generated by API Platform. If your project does not use API Platform yet, follow Integrate API Platform before you continue.

Install feature core

1) Install the required modules

Install the feature and API Platform using Composer:

composer require spryker-feature/product-experience-management:"202608.0" spryker/api-platform:"^1.0.0" --update-with-dependencies
spryker/api-platform

The Product Experience Management feature declares spryker/api-platform as a suggested dependency, not a required one. Install it explicitly, as shown above. Without it, the /products resource is never registered and the endpoint returns 404.

The API registers plugins that ship in modules the feature does not constrain to the versions providing them. Update those modules explicitly:

composer require \
  spryker/category:"^5.25.0" \
  spryker/price-product:"^4.54.0" \
  spryker/product:"^6.57.0" \
  spryker/product-attribute:"^1.22.0" \
  spryker/product-bundle:"^7.29.0" \
  spryker/product-category:"^4.34.0" \
  spryker/product-extension:"^1.6.0" \
  spryker/product-image:"^3.22.0" \
  spryker/shipment-type:"^1.3.0" \
  spryker/stock:"^8.17.0" \
  spryker/tax:"^5.20.0" \
  spryker/tax-product-connector:"^4.13.0" \
  spryker/uuid-behavior:"^1.3.0" \
  --update-with-dependencies
What each version provides
MODULE MINIMUM VERSION PROVIDES
spryker/product ^6.57.0 Collection writers and the plugin stacks registered in step 4
spryker/product-extension ^1.6.0 Interfaces of the collection validator plugins
spryker/category ^5.25.0 CategoryExistsProductAbstractCollection*ValidatorPlugin, spy_category.uuid
spryker/tax ^5.20.0 spy_tax_set.uuid
spryker/tax-product-connector ^4.13.0 TaxSetExistsProductAbstractCollection*ValidatorPlugin
spryker/price-product ^4.54.0 Price collection validator plugins, spy_price_product_store.uuid
spryker/product-image ^3.22.0 Image set expander and validator plugins, spy_product_image_set.uuid
spryker/product-category ^4.34.0 ProductCategoryAbstractCollectionExpanderPlugin
spryker/product-attribute ^1.22.0 SuperAttributeProductConcreteExpanderPlugin
spryker/product-bundle ^7.29.0 ProductBundleProductConcreteCollection*ValidatorPlugin
spryker/shipment-type ^1.3.0 ShipmentTypeExistsProductConcreteCollection*ValidatorPlugin
spryker/stock ^8.17.0 StockProductConcreteCollectionAfterUpdatePlugin, stock validator plugins
spryker/uuid-behavior ^1.3.0 UUID behavior used by the new schema definitions

To use the productClass and shipmentType attributes, install the Self-Service Portal feature as well:

composer require spryker-feature/self-service-portal:"202608.0" --update-with-dependencies
Verification

Make sure the following modules have been installed:

MODULE EXPECTED DIRECTORY
ProductExperienceManagement vendor/spryker-feature/product-experience-management
ApiPlatform vendor/spryker/api-platform

Make sure the installed module versions are at least the ones listed above. If a validator plugin class cannot be found in step 4, the corresponding module is below its minimum version.

2) Set up configuration

The API references categories, tax sets, prices, and image sets by UUID. Enable the UUID columns for those entities.

src/Pyz/Zed/Category/CategoryConfig.php

<?php

namespace Pyz\Zed\Category;

use Spryker\Zed\Category\CategoryConfig as SprykerCategoryConfig;

class CategoryConfig extends SprykerCategoryConfig
{
    /**
     * @return bool
     */
    public function isCategoryUuidEnabled(): bool
    {
        return true;
    }
}

src/Pyz/Zed/PriceProduct/PriceProductConfig.php

<?php

namespace Pyz\Zed\PriceProduct;

use Spryker\Zed\PriceProduct\PriceProductConfig as SprykerPriceProductConfig;

class PriceProductConfig extends SprykerPriceProductConfig
{
    /**
     * @return bool
     */
    public function isPriceProductStoreUuidEnabled(): bool
    {
        return true;
    }
}

src/Pyz/Zed/ProductImage/ProductImageConfig.php

<?php

namespace Pyz\Zed\ProductImage;

use Spryker\Zed\ProductImage\ProductImageConfig as SprykerProductImageConfig;

class ProductImageConfig extends SprykerProductImageConfig
{
    /**
     * @return bool
     */
    public function isProductImageSetUuidEnabled(): bool
    {
        return true;
    }
}

src/Pyz/Zed/Tax/TaxConfig.php

<?php

namespace Pyz\Zed\Tax;

use Spryker\Zed\Tax\TaxConfig as SprykerTaxConfig;

class TaxConfig extends SprykerTaxConfig
{
    /**
     * @return bool
     */
    public function isTaxSetUuidEnabled(): bool
    {
        return true;
    }
}

3) Set up the database schema and transfer objects

Apply database changes and generate entity and transfer changes:

console propel:install
console transfer:generate
Verification

Make sure that the following changes have occurred in the database:

DATABASE ENTITY TYPE EVENT
spy_category.uuid column created
spy_tax_set.uuid column created
spy_price_product_store.uuid column created
spy_product_image_set.uuid column created
spy_stock.uuid column created
Verification

Make sure that the following changes have been applied in transfer objects:

TRANSFER TYPE EVENT PATH
Category.uuid property created src/Generated/Shared/Transfer/CategoryTransfer
MoneyValue.uuid property created src/Generated/Shared/Transfer/MoneyValueTransfer
ProductImageSet.uuid property created src/Generated/Shared/Transfer/ProductImageSetTransfer
StockProduct.stockUuid property created src/Generated/Shared/Transfer/StockProductTransfer
ProductConcrete.validFrom property created src/Generated/Shared/Transfer/ProductConcreteTransfer
ProductConcrete.validTo property created src/Generated/Shared/Transfer/ProductConcreteTransfer
ProductConcrete.productBundle property created src/Generated/Shared/Transfer/ProductConcreteTransfer
ProductConcrete.productClasses property created src/Generated/Shared/Transfer/ProductConcreteTransfer
ProductConcrete.shipmentTypes property created src/Generated/Shared/Transfer/ProductConcreteTransfer
ProductClass class created src/Generated/Shared/Transfer/ProductClassTransfer
ProductForBundle class created src/Generated/Shared/Transfer/ProductForBundleTransfer
ProductAbstractTaxSetCollection class created src/Generated/Shared/Transfer/ProductAbstractTaxSetCollectionTransfer
ProductAbstractRelations.withTaxSet property created src/Generated/Shared/Transfer/ProductAbstractRelationsTransfer

propel:install adds the UUID columns, but it leaves them empty for rows that already exist. Generate the missing values:

console uuid:generate Category spy_category
console uuid:generate Tax spy_tax_set
console uuid:generate PriceProduct spy_price_product_store
console uuid:generate ProductImage spy_product_image_set
console uuid:generate Stock spy_stock

Each command reports how many records it updated. Commands for tables that already have UUIDs report zero records and make no changes.

Existing records

Until you run these commands, existing categories, tax sets, prices, image sets, and warehouses have no UUID. Requests that reference them by UUID fail with a 422 validation error, and read responses return those UUIDs as null.

4) Set up behavior

Register the plugins that validate references and apply cross-module changes for collection operations.

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
SuperAttributeProductConcreteExpanderPlugin Expands concrete products with their super attribute values. None Spryker\Zed\ProductAttribute\Communication\Plugin\Product
ProductImageAbstractCollectionExpanderPlugin Expands abstract product collections with image sets. None Spryker\Zed\ProductImage\Communication\Plugin\Product
ProductCategoryAbstractCollectionExpanderPlugin Expands abstract product collections with category assignments. None Spryker\Zed\ProductCategory\Communication\Plugin\Product
StockProductConcreteCollectionAfterUpdatePlugin Persists stock changes after a concrete product collection is updated. None Spryker\Zed\Stock\Communication\Plugin\Product
PriceProductConcreteCollectionCreateValidatorPlugin Validates prices when concrete products are created. None Spryker\Zed\PriceProduct\Communication\Plugin\Product
ProductImageSetExistsProductConcreteCollectionCreateValidatorPlugin Validates that referenced image sets exist. None Spryker\Zed\ProductImage\Communication\Plugin\Product
StockExistsProductConcreteCollectionCreateValidatorPlugin Validates that referenced warehouses exist. None Spryker\Zed\Stock\Communication\Plugin\Product
ShipmentTypeExistsProductConcreteCollectionCreateValidatorPlugin Validates that referenced shipment types exist. None Spryker\Zed\ShipmentType\Communication\Plugin\Product
ProductClassExistsProductConcreteCollectionCreateValidatorPlugin Validates that referenced product classes exist. Self-Service Portal feature SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\Product
ProductBundleProductConcreteCollectionCreateValidatorPlugin Validates bundled product assignments. None Spryker\Zed\ProductBundle\Communication\Plugin\Product
CategoryExistsProductAbstractCollectionCreateValidatorPlugin Validates that referenced categories exist. None Spryker\Zed\Category\Communication\Plugin\Product
TaxSetExistsProductAbstractCollectionCreateValidatorPlugin Validates that referenced tax sets exist. None Spryker\Zed\TaxProductConnector\Communication\Plugin\Product
PriceProductAbstractCollectionCreateValidatorPlugin Validates prices when abstract products are created. None Spryker\Zed\PriceProduct\Communication\Plugin\Product

The update validator plugins mirror the create validator plugins and live in the same namespaces.

Add the plugins to ProductDependencyProvider. getProductConcreteExpanderPlugins() and getProductAbstractCollectionExpanderPlugins() already exist in most projects — add the listed plugins to the stacks you have instead of replacing them.

src/Pyz/Zed/Product/ProductDependencyProvider.php

<?php

namespace Pyz\Zed\Product;

use Spryker\Zed\Category\Communication\Plugin\Product\CategoryExistsProductAbstractCollectionCreateValidatorPlugin;
use Spryker\Zed\Category\Communication\Plugin\Product\CategoryExistsProductAbstractCollectionUpdateValidatorPlugin;
use Spryker\Zed\PriceProduct\Communication\Plugin\Product\PriceProductAbstractCollectionCreateValidatorPlugin;
use Spryker\Zed\PriceProduct\Communication\Plugin\Product\PriceProductConcreteCollectionCreateValidatorPlugin;
use Spryker\Zed\PriceProduct\Communication\Plugin\Product\PriceProductConcreteCollectionUpdateValidatorPlugin;
use Spryker\Zed\Product\ProductDependencyProvider as SprykerProductDependencyProvider;
use Spryker\Zed\ProductAttribute\Communication\Plugin\Product\SuperAttributeProductConcreteExpanderPlugin;
use Spryker\Zed\ProductBundle\Communication\Plugin\Product\ProductBundleDeactivatorProductConcreteAfterUpdatePlugin;
use Spryker\Zed\ProductBundle\Communication\Plugin\Product\ProductBundleProductConcreteAfterUpdatePlugin;
use Spryker\Zed\ProductBundle\Communication\Plugin\Product\ProductBundleProductConcreteCollectionCreateValidatorPlugin;
use Spryker\Zed\ProductBundle\Communication\Plugin\Product\ProductBundleProductConcreteCollectionUpdateValidatorPlugin;
use Spryker\Zed\ProductCategory\Communication\Plugin\Product\ProductCategoryAbstractCollectionExpanderPlugin;
use Spryker\Zed\ProductImage\Communication\Plugin\Product\ProductImageAbstractCollectionExpanderPlugin;
use Spryker\Zed\ProductImage\Communication\Plugin\Product\ProductImageSetExistsProductConcreteCollectionCreateValidatorPlugin;
use Spryker\Zed\ProductImage\Communication\Plugin\Product\ProductImageSetExistsProductConcreteCollectionUpdateValidatorPlugin;
use Spryker\Zed\PriceProduct\Communication\Plugin\ProductConcrete\PriceProductConcreteAfterUpdatePlugin;
use Spryker\Zed\ProductImage\Communication\Plugin\ProductConcreteAfterUpdatePlugin as ImageSetProductConcreteAfterUpdatePlugin;
use Spryker\Zed\ProductSearch\Communication\Plugin\ProductConcrete\ProductSearchProductConcreteAfterUpdatePlugin;
use Spryker\Zed\ProductValidity\Communication\Plugin\ProductValidityUpdatePlugin;
use Spryker\Zed\ShipmentType\Communication\Plugin\Product\ShipmentTypeExistsProductConcreteCollectionCreateValidatorPlugin;
use Spryker\Zed\ShipmentType\Communication\Plugin\Product\ShipmentTypeExistsProductConcreteCollectionUpdateValidatorPlugin;
use Spryker\Zed\Stock\Communication\Plugin\Product\StockExistsProductConcreteCollectionCreateValidatorPlugin;
use Spryker\Zed\Stock\Communication\Plugin\Product\StockExistsProductConcreteCollectionUpdateValidatorPlugin;
use Spryker\Zed\Stock\Communication\Plugin\Product\StockProductConcreteCollectionAfterUpdatePlugin;
use Spryker\Zed\TaxProductConnector\Communication\Plugin\Product\TaxSetExistsProductAbstractCollectionCreateValidatorPlugin;
use Spryker\Zed\TaxProductConnector\Communication\Plugin\Product\TaxSetExistsProductAbstractCollectionUpdateValidatorPlugin;
use Spryker\Zed\TaxProductConnector\Communication\Plugin\Product\TaxSetProductAbstractCollectionExpanderPlugin;
use SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\Product\ProductClassExistsProductConcreteCollectionCreateValidatorPlugin;
use SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\Product\ProductClassExistsProductConcreteCollectionUpdateValidatorPlugin;
use SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\Product\ProductClassProductConcreteAfterUpdatePlugin;
use SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\Product\ShipmentTypeProductConcretePostUpdatePlugin;

class ProductDependencyProvider extends SprykerProductDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductConcreteExpanderPluginInterface>
     */
    protected function getProductConcreteExpanderPlugins(): array
    {
        return [
            // Keep the plugins your project already registers.
            new SuperAttributeProductConcreteExpanderPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductAbstractCollectionExpanderPluginInterface>
     */
    protected function getProductAbstractCollectionExpanderPlugins(): array
    {
        return [
            new TaxSetProductAbstractCollectionExpanderPlugin(),
            new ProductImageAbstractCollectionExpanderPlugin(),
            new ProductCategoryAbstractCollectionExpanderPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\Product\Dependency\Plugin\ProductConcretePluginUpdateInterface>
     */
    protected function getProductConcreteAfterUpdateCollectionPlugins(): array
    {
        return [
            new ImageSetProductConcreteAfterUpdatePlugin(),
            new PriceProductConcreteAfterUpdatePlugin(),
            new StockProductConcreteCollectionAfterUpdatePlugin(),
            new ProductSearchProductConcreteAfterUpdatePlugin(),
            new ProductValidityUpdatePlugin(),
            new ProductClassProductConcreteAfterUpdatePlugin(),
            new ShipmentTypeProductConcretePostUpdatePlugin(),
            new ProductBundleProductConcreteAfterUpdatePlugin(),
            new ProductBundleDeactivatorProductConcreteAfterUpdatePlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductConcreteCollectionCreateValidatorPluginInterface>
     */
    protected function getProductConcreteCollectionCreateValidatorPlugins(): array
    {
        return [
            new PriceProductConcreteCollectionCreateValidatorPlugin(),
            new ProductImageSetExistsProductConcreteCollectionCreateValidatorPlugin(),
            new StockExistsProductConcreteCollectionCreateValidatorPlugin(),
            new ShipmentTypeExistsProductConcreteCollectionCreateValidatorPlugin(),
            new ProductClassExistsProductConcreteCollectionCreateValidatorPlugin(),
            new ProductBundleProductConcreteCollectionCreateValidatorPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductConcreteCollectionUpdateValidatorPluginInterface>
     */
    protected function getProductConcreteCollectionUpdateValidatorPlugins(): array
    {
        return [
            new PriceProductConcreteCollectionUpdateValidatorPlugin(),
            new ProductImageSetExistsProductConcreteCollectionUpdateValidatorPlugin(),
            new StockExistsProductConcreteCollectionUpdateValidatorPlugin(),
            new ShipmentTypeExistsProductConcreteCollectionUpdateValidatorPlugin(),
            new ProductClassExistsProductConcreteCollectionUpdateValidatorPlugin(),
            new ProductBundleProductConcreteCollectionUpdateValidatorPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductAbstractCollectionCreateValidatorPluginInterface>
     */
    protected function getProductAbstractCollectionCreateValidatorPlugins(): array
    {
        return [
            new CategoryExistsProductAbstractCollectionCreateValidatorPlugin(),
            new TaxSetExistsProductAbstractCollectionCreateValidatorPlugin(),
            new PriceProductAbstractCollectionCreateValidatorPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\ProductExtension\Dependency\Plugin\ProductAbstractCollectionUpdateValidatorPluginInterface>
     */
    protected function getProductAbstractCollectionUpdateValidatorPlugins(): array
    {
        return [
            new CategoryExistsProductAbstractCollectionUpdateValidatorPlugin(),
            new TaxSetExistsProductAbstractCollectionUpdateValidatorPlugin(),
        ];
    }
}
Verification

If a validator plugin is missing, the API accepts references to entities that do not exist and the write fails later with a database error instead of a 422 validation error. If an expander plugin is missing, the corresponding data is absent from read responses.

5) Generate the API resources

Clear the Glue Backend cache and regenerate the API resources:

rm -rf data/cache/GlueBackend/<environment>
docker/sdk cli "GLUE_APPLICATION=GLUE_BACKEND vendor/bin/glue api:generate"
docker/sdk cli "GLUE_APPLICATION=GLUE_BACKEND vendor/bin/glue cache:clear"

Replace <environment> with your environment name, for example, development.

Verification

Request a Back Office access token as described in Authenticate as a Back Office user, then retrieve a product collection:

curl "https://glue-backend.mysprykershop.com/products?perPage=1" \
  -H "Authorization: Bearer {access_token}"

The integration is successful when the request returns 200 with a data array. A 404 means the resource was not generated — repeat step 5 and confirm that spryker/api-platform is installed. A 401 means the access token is missing or expired.