Deliver personalized experiences through localized content and relevant pricing.
After completing this page, you should be able to:
- Explain how to use the
localeProjection
parameter in the Product Projections API to retrieve product content tailored to a specific locale. - Describe how to leverage Product Price Selection parameters (for example,
priceCurrency
,priceCountry
,storeProjection
,priceChannel
) to retrieve accurate and context-specific pricing and inventory information using the Product Projections API.
storeProjection
in the Product Projection API, you can retrieve tailored data that enhances the customer's journey.This page will guide you through the key aspects of contextual data, focusing on locale awareness to present content in the customer's language and price and inventory selection to display relevant pricing and availability. Understanding and implementing these techniques can lead to increased customer engagement and a more seamless shopping experience.
Locale awareness
localeProjection
query parameter allows you to retrieve one language with the matching localized fields.localeProjection
with a locale code (for example, en-US
, de-DE
), you instruct the API to return only the name
, description
, slug
, and other localized fields for that specific language.Code example
localeProjection
parameter is explicitly set to ensure that the returned product fields are in the correct language:
/**
* Fetches a localized product projection using slug and localeProjection.
* @param slug - The slug to search for.
* @param locale - The locale for localization.
* @returns The localized product projection.
*/
async fetchLocalizedProductBySlug(
slug: string,
locale: string,
): Promise<ProductProjection> {
try {
const response = (await apiRoot
.productProjections()
.get({
queryArgs: {
where: `slug(${locale} = "${slug}")`,
staged: false,
localeProjection: locale, // Only return fields in the given locale
withTotal: false
},
})
.execute()) as ClientResponse<ProductProjectionPagedQueryResponse>;
const product = response.body.results?.[0];
if (!product) {
throw new NotFoundException(
`Localized product with slug '${slug}' not found.`,
);
}
return product;
} catch (error) {
this.handleServiceError(error, `localized slug '${slug}'`);
}
}
Key takeaways
localeProjection: locale
: filters localized fields to return only those matching the specified locale (for example,en-US
).where: slug(${locale} = "...")
: ensures you are querying the correct localized slug.- Benefit: provides precise localization, improving clarity and trust for your customers.
Accurate price and inventory selection
priceCurrency
, priceCountry
, storeProjection
, and priceChannel
. Using these parameters ensures the API response identifies the single, best-matching price for that specific context.priceCurrency
: specifies the currency in which the price should be displayed (for example,"AUD"
).priceCountry
: filters prices based on the country (for example,"AU"
), ensuring regional pricing compliance.storeProjection
: ensures returned data (prices, inventory) matches the configuration of the specified store. This allows for store-specific visibility and pricing. This will also return any Tailored Product data.priceChannel
: filters product prices based on the provided price channel.
Code example
Below is a sample code snippet that demonstrates how to fetch a product with pricing and inventory tailored to the customer's context in your Backend-for-Frontend (BFF) layer:
/**
* Fetches a product projection with price and inventory information.
* @param slug - Product slug.
* @param locale - Customer locale.
* @param priceCurrency - Currency code (e.g., "AUD").
* @param priceCountry - Country code (e.g., "AU").
* @param storeKey - Store key.
* @param priceChannelId - Channel ID for pricing.
* @returns The enriched product projection.
*/
async fetchProductWithPriceAndInventory(
slug: string,
locale: string,
priceCurrency: string,
priceCountry: string,
storeKey: string,
priceChannelId: string,
): Promise<ProductProjection> {
try {
const response = (await apiRoot
.productProjections()
.get({
queryArgs: {
where: `slug(${locale} = "${slug}")`,
staged: false,
withTotal: false,
localeProjection: locale,
priceCurrency,
priceCountry,
storeProjection: storeKey,
priceChannel: priceChannelId,
},
})
.execute()) as ClientResponse<ProductProjectionPagedQueryResponse>;
const product = response.body.results?.[0];
if (!product) {
throw new NotFoundException(`Product with slug '${slug}' not found.`);
}
this.logger.log(
`Fetched product with pricing: ${JSON.stringify(product)}`,
);
return product;
} catch (error) {
this.handleServiceError(error, `slug '${slug}' with price & inventory`);
}
}
Key takeaways
- Using Product Price Selection parameters will return a new field
price
with the single best matching value. Note that theprices
field is also still returned, but will be filtered by the Store distribution channels.
Execute and observe the response
fetchProductWithPriceAndInventory()
, and inspect the JSON response.When executed correctly, the response will include important context-aware fields based on your query parameters. Here's a sample response:
{
"id": "2bbb247c-9d08-4018-b3ee-07500af7b50b",
"version": 15,
"name": {
"en-US": "Charlie Armchair"
},
"description": {
"en-US": "A corduroy chair with wooden legs has a cozy and rustic feel..."
},
"slug": {
"en-US": "charlie-armchair"
},
"masterVariant": {
"sku": "CARM-023",
"prices": [
{
"id": "aa950e94-f3dd-4855-b621-4db87110c96f",
"value": {
"currencyCode": "USD",
"centAmount": 49900
},
"country": "US",
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 42415
}
}
},
{
"id": "bb0d9bd1-09a5-4269-97a2-5d090fe85829",
"value": {
"currencyCode": "USD",
"centAmount": 8900
},
"country": "US",
"channel": {
"typeId": "channel",
"id": "ba248022-a551-4923-ad18-e310ea215ac6"
},
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 7565
}
}
}
],
"price": {
"id": "d9e53b55-b46f-4ed0-9946-b2de11736504",
"value": {
"currencyCode": "USD",
"centAmount": 30000
},
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 25500
}
},
"channel": {
"typeId": "channel",
"id": "ba248022-a551-4923-ad18-e310ea215ac6"
}
},
"availability": {
"isOnStock": true,
"availableQuantity": 100,
"channels": {
"58facf50-1a75-480b-b6ad-cea4df710a82": {
"isOnStock": true,
"availableQuantity": 50
}
}
}
}
}
fetchProductWithPriceAndInventory
, the Product Projection API dynamically adjusts the returned Product data based on your provided context—locale, currency, country, store, and price channel.Here’s how to interpret key fields in the response:
- Localized fields (
name
,description
,slug
,attributes
):- Only fields for the specified locale (for example,
"en-US"
) are included in the response becauselocaleProjection
is explicitly passed. You can use Locales orstoreProjection
to filter the locale response. Remember that a Store can have multiple locales, whilst thelocales
parameter is always one.
- Only fields for the specified locale (for example,
"name": {
"en-US": "Charlie Armchair"
}
- Why this matters: this ensures your frontend UI displays only the customer’s preferred language and avoids unintentional fallbacks or language switching logic on the frontend.
- Prices array (
masterVariant.prices
):- The
prices
array contains only prices relevant to the provided contextstoreProjection
. - This is a filtered subset of all available prices. For example, while the product may have prices for
EUR
,GBP
, andUSD
in the backend, this response includes onlyUSD
prices applicable to the U.S. market and to a specific Store/Channel.
- The
"prices": [
{
"id": "aa950e94-f3dd-4855-b621-4db87110c96f",
"value": {
"currencyCode": "USD",
"centAmount": 49900
},
"country": "US",
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 42415
}
}
},
{
"id": "bb0d9bd1-09a5-4269-97a2-5d090fe85829",
"value": {
"currencyCode": "USD",
"centAmount": 8900
},
"country": "US",
"channel": {
"typeId": "channel",
"id": "ba248022-a551-4923-ad18-e310ea215ac6"
},
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 7565
}
}
}
]
- Selected
price
field- The
price
field reflects the best-matching price automatically chosen by Composable Commerce. - This is calculated based on price selection precedence, considering currency, country, Store, and Channel.
- If a discounted price is applicable (based on rules and active product discounts), it appears inside
discounted
.
- The
"price": {
"id": "d9e53b55-b46f-4ed0-9946-b2de11736504",
"value": {
"currencyCode": "USD",
"centAmount": 30000
},
"discounted": {
"value": {
"currencyCode": "USD",
"centAmount": 25500
}
},
"channel": {
"typeId": "channel",
"id": "ba248022-a551-4923-ad18-e310ea215ac6"
}
}
- Inventory / availability (
availability
)- The response shows stock status for both global and Store-specific inventory (per
storeProjection
). - Inside
channels
, you’ll find inventory details specific to your store’s supply channel.
- The response shows stock status for both global and Store-specific inventory (per
"availability": {
"isOnStock": true,
"availableQuantity": 100,
"channels": {
"58facf50-...": {
"isOnStock": true,
"availableQuantity": 50
}
}
}