Using the composable catalog with your storefront
Learn how a company with a single base catalog can use the composable catalog data model (CCDM) APIs to add product data, define catalogs using projections, and retrieve the catalog data for display in a headless storefront.
This end-to-end use case demonstrates how a company with a single base catalog can use CCDM to:
- Support products across two geographical markets and two distinct brands.
- Support pricing by customer segment
- Enhance operational efficiency by avoiding duplication of product catalogs across multiple websites.
- Control product visibility on websites based on geographical market and brand specifications.
Before you begin, review set up and manage catalogs to understand the channel and policy concepts.
Let's get started
This use case demonstrates an end-to-end workflow for using CCDM based on the following components:
One Channel
A distribution channel for
Zenith Automotive
is linked to a single scope, (locale:en-US
) and two policies.- The Location policy (Policy 1) that defines the catalog projection for
locations
. This projection can be used to specify a target geographic market for selling specified SKUs. For example:UK
,USA
. - The Brand policy (Policy 2) defines the catalog projection for
brands
. This projection can be used to specify one or more brands associated with each product SKU, for exampleAurora
,Bolt
.
- The Location policy (Policy 1) that defines the catalog projection for
Two products SKUs
Aurora Prism battery
belongs toAurora
and is aimed to be sold inUSA
.Bolt Atlas battery
belongs toBolt
and is aimed to be sold inUK
.- The values for brand and location are product attributes of each SKU.
- The product specification does not include any
Channel
orPolicy
values. When retrieving products using the Storefront API, Channel and Policy values are passed in using API headers.
Storefront APIs
- A catalog service API endpoint is used to represent how data will be retrieved using CCDM concepts.
- Pay close attention to the API headers. The CCDM concepts for filtering products using channels and policies are implemented through the API headers.
- Two Storefront API calls are represented:
- API Call one: Returns a SKU for
Aurora
andUSA
combination. - API Call two: Returns a SKU for
Bolt
andUK
combination.
- API Call one: Returns a SKU for
In the steps below, you use CCDM APIs to add the product, channel, and policy data to the SaaS data space for your project. Then, you use the the Storefront API to retrieve the product data based on brand and location attributes.
Step 1. Add products to your catalog
Add two simple products, "Aurora Prism battery" and "Bolt Atlas battery" and the product attribute metadata to define the search and display characteristics for the brand and country attributes.
Add product attribute metadata
Create the metadata to define the search characteristics and filters for displaying location and brand attributes on the storefront by submitting a Create product attribute metadata POST request.
Request
Copied to your clipboardcurl --request POST \--url https://commerce.adobe.io/api/v1/catalog/products/metadata/<DATA_SPACE_ID> \--header 'Content-Type: application/json' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '[{"code": "brand","scope": {"locale": "en-US"},"label": "Brand","dataType": "TEXT","visibleIn": ["PRODUCT_DETAIL","PRODUCT_LISTING","SEARCH_RESULTS","PRODUCT_COMPARE"],"filterable": true,"sortable": false,"searchable": true,"searchWeight": 55,"searchTypes": ["AUTOCOMPLETE","CONTAINS","STARTS_WITH"]},{"code": "country","scope": {"locale": "en-US"},"label": "Country","dataType": "TEXT","visibleIn": ["PRODUCT_DETAIL","PRODUCT_LISTING","SEARCH_RESULTS","PRODUCT_COMPARE"],"filterable": true,"sortable": false,"searchable": true,"searchWeight": 55,"searchTypes": ["AUTOCOMPLETE","CONTAINS","STARTS_WITH"]}]'
Add products
Add products by submitting a createProducts POST request using the Data Ingestion API.
Create Aurora product
Add the simple product Aurora Prism Battery with two attribute codes, Brand
set to Aurora, and Country
set to USA by sending the following payload in the Create products request.
Create product request
Copied to your clipboardcurl --request POST \--url https://commerce.adobe.io/api/v1/catalog/products/<DATA_SPACE_ID> \--header 'Content-Type: application/json' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '[{"sku": "aurora_prism_battery","scope": {"locale": "en-US"},"name": "Aurora prism battery","slug": "vehicle-battery","status": "ENABLED","description": "Vehicle battery sold only in USA","shortDescription": "battery","roles": ["SEARCH","CATALOG"],"metaTags": {"title": " ","description": "Zenith Automotive Vehicles and Parts","keywords": ["battery","part"]},"attributes": [{"code": "Brand","type": "STRING","values": ["Aurora"]},{"code": "Country","type": "ARRAY","values": ["USA"]}],"images": [{"url": "https://picsum.photos/300/200","label": "aurora prism battery photo","roles": ["PDP","PLP"]}],"links": [{"sku": "aurora-prism-2025","type": "related"}],"routes": [{"path": "aurora-prism-battery"},{"path": "vehicles/aurora-prism/parts","position": 1}]}]'
Create Bolt product
Add the product Bolt Atlas Battery with two attribute codes, Brand
set to Bolt, and Country
set to UK by sending the following payload in the createProducts request.
Create product request
Copied to your clipboardcurl --request POST \--url https://commerce.adobe.io/api/v1/catalog/products/<DATA_SPACE_ID> \--header 'Content-Type: application/json' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '[{"sku": "bolt_atlas_battery","scope": {"locale": "en-US"},"name": "Bolt Atlas battery","slug": "vehicle-battery","status": "ENABLED","description": "Vehicle battery sold only in UK","shortDescription": "battery","roles": ["SEARCH","CATALOG"],"metaTags": {"title": " ","description": "Zenith Automotive Vehicles and Parts","keywords": ["battery","part"]},"attributes": [{"code": "Brand","type": "STRING","values": ["Bolt"]},{"code": "Country","type": "ARRAY","values": ["UK"]}],"images": [{"url": "https://picsum.photos/300/200","label": "bolt atlas battery photo","roles": ["PDP","PLP"]}],"links": [{"sku": "bolt-atlas-2025","type": "related"}],"routes": [{"path": "bolt-atlas-battery"},{"path": "vehicles/bolt-atlas/parts","position": 1}]}]'
Step 2. Define the channel and policies
In this step, create a channel for Zenith Automotive
using the Create channel GraphQL mutation from the Catalog Management API.
Assign two policies to the channel: a Location Policy
with "USA" and "UK" as locations, and a Brand Policy
with "Aurora" and "Bolt".
Create policies
Use the createPolicy GraphQL mutation from the Catalog Management API to define the location and brand policies.
The query response returns a PolicyId
value that is required when you assign the policy to a channel.
Location policy
cURL Request
Copied to your clipboardcurl --request POST \--url http://commerce.adobe.io/admin/graphql \--header 'Content-Type: application/json' \--header 'AC-Environment-Id: <DATA_SPACE_ID>' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '{"query":"mutation createPolicy {\n\tcreatePolicy(\n\t\tpolicyRequest: {\n\t\t\tname: \"Location Policy\"\n\t\t\tmandatory: true\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\ttriggers: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"AC-Policy-Country\",\n\t\t\t\t\t\t\ttransportType: HTTP_HEADER\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t\tfilters: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tattribute: \"country\",\n\t\t\t\t\t\t\tvalueSource: TRIGGER,\n\t\t\t\t\t\t\tvalue: \"AC-Policy-Country\"\n\t\t\t\t\t\t\tactionFilterOperator: EQUALS,\n\t\t\t\t\t\t\tenabled: true\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t) {\n\t\tname\n\t\tpolicyId\n\t\tmandatory\n\t\tactions {\n\t\t\ttriggers {\n\t\t\t\tname\n\t\t\t\ttransportType\n\t\t\t}\n\t\t\tfilters {\n\t\t\t\tattribute\n\t\t\t\tvalueSource\n\t\t\t\tvalue\n\t\t\t\tactionFilterOperator\n\t\t\t\tenabled\n\t\t\t}\n\t\t}\n\t\tcreatedAt\n\t\tupdatedAt\n\t}\n}\n","operationName":"createPolicy"}'
Payload
Copied to your clipboardmutation CreatePolicy {createPolicy(policyRequest: {name: "Location policy",mandatory: true,actions: [{triggers: [{name: "AC-Policy-Country",transportType: HTTP_HEADER}],filters: [{attribute: "country",valueSource: TRIGGER,value: "AC-Policy-Country",actionFilterOperator: EQUALS,enabled: true}]}]}) {namepolicyIdmandatoryactions {triggers {nametransportType}filters {attributevalueSourcevalueactionFilterOperatorenabled}}createdAtupdatedAt}}
Response
Copied to your clipboard{"data": {"createPolicy": {"policyId": "56a6908f-eyx3-59c3-sye8-574509e6y45","name": "Location policy","mandatory": true,"actions": [{"triggers": [{"transportType": "HTTP_HEADER","name": "AC-Policy-Country"}],"filters": [{"attribute": "country","actionFilterOperator": "EQUALS","value": "AC-Policy-Country","enabled": true,"valueSource": "TRIGGER"}]}],"createdAt": "2024-11-12T12:00:16.146157","updatedAt": "2024-11-12T12:00:16.146157"}},"extensions": {"request-id": "bbcdcbc79b5d873b"}}
Brand policy
cURL Request
Copied to your clipboardcurl --request POST \--url http://commerce.adobe.io/admin/graphql \--header 'Content-Type: application/json' \--header 'AC-Environment-Id: <DATA_SPACE_ID>' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '{"query":"mutation createPolicy {\n\tcreatePolicy(\n\t\tpolicyRequest: {\n\t\t\tname: \"Brand Policy\"\n\t\t\tmandatory: true\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\ttriggers: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"AC-Policy-Brand\",\n\t\t\t\t\t\t\ttransportType: HTTP_HEADER\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t\tfilters: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tattribute: \"brand\",\n\t\t\t\t\t\t\tvalueSource: TRIGGER,\n\t\t\t\t\t\t\tvalue: \"AC-Policy-Brand\"\n\t\t\t\t\t\t\tactionFilterOperator: EQUALS,\n\t\t\t\t\t\t\tenabled: true\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t) {\n\t\tname\n\t\tpolicyId\n\t\tmandatory\n\t\tactions {\n\t\t\ttriggers {\n\t\t\t\tname\n\t\t\t\ttransportType\n\t\t\t}\n\t\t\tfilters {\n\t\t\t\tattribute\n\t\t\t\tvalueSource\n\t\t\t\tvalue\n\t\t\t\tactionFilterOperator\n\t\t\t\tenabled\n\t\t\t}\n\t\t}\n\t\tcreatedAt\n\t\tupdatedAt\n\t}\n}\n","operationName":"createPolicy"}'
Payload
Copied to your clipboardmutation CreatePolicy {createPolicy(policyRequest: {name: "Brand policy",mandatory: true,actions: [{triggers: [{name: "AC-Policy-Brand",transportType: HTTP_HEADER}],filters: [{attribute: "brand",valueSource: TRIGGER,value: "AC-Policy-Brand",actionFilterOperator: EQUALS,enabled: true}]}]}) {namepolicyIdmandatoryactions {triggers {nametransportType}filters {attributevalueSourcevalueactionFilterOperatorenabled}}createdAtupdatedAt}}
Response
Copied to your clipboard{"data": {"createPolicy": {"policyId": "39c8106d-aab2-49b2-aac3-177608d4e567","name": "Brand policy","mandatory": true,"actions": [{"triggers": [{"transportType": "HTTP_HEADER","name": "AC-Policy-Brand"}],"filters": [{"attribute": "country","actionFilterOperator": "EQUALS","value": "AC-Policy-Country","enabled": true,"valueSource": "TRIGGER"}]}],"createdAt": "2024-11-12T12:00:16.146157","updatedAt": "2024-11-12T12:00:16.146157"}},"extensions": {"request-id": "bbcdcbc79b5d6788b"}}
Create channel
Create a channel and assign the policies for location filtering and brand filtering. Use the policy IDs returned in the createPolicy
request to assign the policies.
cURL Request
Copied to your clipboardcurl --request POST \--url http://commerce.adobe.io/admin/graphql \--header 'Content-Type: application/json' \--header 'AC-Environment-Id: <DATA_SPACE_ID>' \--header 'x-api-key: <API_KEY>' \--header 'x-gw-signature: <JWT_TOKEN>' \--data '{"query":"mutation {\n createChannel(\n channelRequest: {\n name: \"Zenith Automotive\",\n scopes: [\n { locale: \"en-US\" }\n ]\n policyIds : [\n \"56a6908f-eyx3-59c3-sye8-574509e6y45\",\n \"39c8106d-aab2-49b2-aac3-177608d4e567\",\n ]\n }\n ) {\n channelId\n name\n scopes {\n locale\n }\n policyIds\n createdAt\n updatedAt\n }\n}"}'
Payload
Copied to your clipboardmutation {createChannel(channelRequest: {name: "Zenith Automotive",scopes: [{ locale: "en-US" }]policyIds : ["56a6908f-eyx3-59c3-sye8-574509e6y45","39c8106d-aab2-49b2-aac3-177608d4e567",]}) {channelIdnamescopes {locale}policyIdscreatedAtupdatedAt}}
Response
Copied to your clipboard{"data": {"channels": [{"name": "Zenith Automotive","channelId": "b726c1e9-2842-4ab5-9b19-ca65c23bbb3b","scopes": [{"locale": "en-US"}],"policyIds": ["56a6908f-eyx3-59c3-sye8-574509e6y45","39c8106d-aab2-49b2-aac3-177608d4e567",],"createdAt": "2024-12-04T19:18:46.075","updatedAt": "2024-12-16T21:17:00.793"}]}}
Step 3. Retrieve SKUs
Use the Storefront GraphQL API productSearch query to retrieve the SKUs you created.
Send GraphQL requests for Storefront APIs to the following endpoints:
- Sandbox:
https://catalog-service-sandbox.adobe.io/graphql
- Production:
https://catalog-service.adobe.io/graphql
When you submit requests using Storefront API, use the Adobe Commerce API key for your production environment to authenticate to both sandbox and production endpoints.
Retrieve SKU for the Aurora
brand
Retrieve the SKU you created for Aurora
where location is USA
. Use the search phrase Zenith Automotive Vehicles and Parts
, and specify a page size to limit results.
The brand and location (AC-Policy-Brand
and AC-Policy-Country
) are passed in using Storefront API headers.
Use the following headers in the request:
Copied to your clipboardAC-Channel-Id: 'b726c1e9-2842-4ab5-9b19-ca65c23bbb3b'AC-Environment-Id: '<DATA_SPACE_ID>'AC-Policy-Brand: 'Aurora'AC-Policy-Country: 'US'AC-Price-Book-Id: 'base'AC-Scope-Locale: 'en-US'Content-Type: 'application/json'X-Api-Key: '<API-KEY>'
Request
Copied to your clipboardquery getProductSearchDetails($filter: [SearchClauseInput!],$phrase: String!,$sort: [ProductSearchSortInput!],$pageSize: Int!) {productSearch(phrase: $phrase,filter: $filter,sort: $sort,page_size: $pageSize) {items {productView {skunameslugstatusimageslinksattributesdescriptionshortDescriptionroutesroles... on SimpleProductView {price {final {amount {valuecurrency}}regular {amount {valuecurrency}}}}}}}}
Here are the variable values to specify in the request:
Copied to your clipboard{"phrase": "Zenith Automotive Vehicles and Parts","filter": [],"sort": [],"pageSize": 100}
Response
The response returns the product details for a single SKU, Aurora Prism battery
.
Copied to your clipboard{"errors": [],"data": {"productSearch": {"items": [{"productView": {"sku": "aurora_prism_battery","name": "Aurora Prism battery","images": [{"url": "https://picsum.photos/300/200","label": "aurora prism battery photo","roles": ["PDP","PLP"]}],"links": [{"sku": "aurora-prism-2025","type": "related"}],"attributes": [{"code": "Brand","type": "STRING","values": ["Aurora"]},{"code": "country","type": "ARRAY","values": ["USA"]}],"description": "Zenith Automotive Vehicles and Parts","shortDescription": "battery","routes": [{"path": "aurora-prism-battery"},{"path": "vehicles/aurora-prism/parts","position": 1}{"path": "aurora-prism-battery"},],"roles": ["SEARCH","CATALOG"],"metaTags": {"title": " ","description": "Zenith Automotive Vehicles and Parts","keywords": ["battery","part"]},"price": {"final": {"amount": {"value": "2099.00","currency": "USD"}},"regular": {"amount": {"value": "2299.00","currency": "USD"}}}}}]}}}
Retrieve SKU for the Bolt brand
Retrieve the SKU you created for Bolt
where location is UK
. Use the search phrase Zenith Automotive Vehicles and Parts
, and specify a page size to limit results.
The brand and location (AC-Policy-Brand
and AC-Policy-Country
) are passed in using the Storefront API headers.
Use the following headers in the request:
Copied to your clipboardAC-Channel-Id: 'b726c1e9-2842-4ab5-9b19-ca65c23bbb3b'AC-Environment-Id: '<DATA_SPACE_ID>'AC-Policy-Brand: 'Bolt'AC-Policy-Country: 'UK'AC-Price-Book-Id: 'base'AC-Scope-Locale: 'en-US'Content-Type: 'application/json'X-Api-Key: '<API-KEY>'
Request
Copied to your clipboardquery getProductSearchDetails($filter: [SearchClauseInput!],$phrase: String!,$sort: [ProductSearchSortInput!],$pageSize: Int!) {productSearch(phrase: $phrase,filter: $filter,sort: $sort,page_size: $pageSize) {items {productView {skunameimageslinksattributesdescriptionshortDescriptionroutesroles... on SimpleProductView {price {final {amount {valuecurrency}}regular {amount {valuecurrency}}}}}}}}
Here are the variables specified in the request:
Copied to your clipboard{"phrase": "Zenith Automotive Vehicles and Parts","filter": [],"sort": [],"pageSize": 100}
Response
The response returns the product details for a single SKU, Bolt Atlas battery
.
Copied to your clipboard{"errors": [],"data": {"productSearch": {"items": [{"productView": {"sku": "bolt_atlas_battery","name": "Bolt Atlas battery","images": [{"url": "https://picsum.photos/400/200","label": "Bolt Atlas Battery photo","roles": ["PDP","PLP"]}],"links": [{"sku": "bolt-atlas-2025","type": "related"}],"attributes": [{"code": "Brand","type": "STRING","values": ["Bolt"]},{"code": "Country","type": "ARRAY","values": ["UK"]}],"description": "Vehicle battery sold only in UK","shortDescription": "battery","images": [{"url": "https://picsum.photos/300/200","label": "bolt atlas battery photo","roles": ["PDP","PLP"]}],"links": [{"sku": "bolt-atlas-2025","type": "related"}],"routes": [{"path": "bolt-atlas-battery"},{"path": "vehicles/bolt-atlas/parts","position": 1}],"roles": ["SEARCH","CATALOG"],"price": {"final": {"amount": {"value": "1769.00","currency": "GBP"}},"regular": {"amount": {"value": "1899.00","currency": "GBP"}}}}}]}}}