Learn how to retrieve Product data by querying it by ID, key, and slug.
After completing this page, you should be able to:
- Identify the three primary methods for fetching product data using the TypeScript SDK: by ID, Key, and slug.
- Understand the typical use cases for each fetching method in a digital commerce context like Zen Electron.
- Recognize the core parameters and SDK methods used for each approach.
- Determine the appropriate fetching method based on a given scenario.
When building a PDP, we need to efficiently query the Product Projection API. Composable Commerce offers many ways to do this and different options are available depending on the page context.
- If you need to fetch the Product Projection when a user directly visits the PDP from a URL (for example,
www.electronics-high-tech.com.au/en-nz/p/iphone-16
), then you will need to query by the Product Projection slug. - If you need to fetch the Product Projection when a user navigates to the PDP from within the website, example search results, product card, category page or Content Management System page, then you can use the Product Projection ID, Key, or slug. You may have more options in this scenario if your application is storing these values on the client.
Scenario 1: Direct access via URL
-
Context: a user enters the PDP URL directly into their browser or follows an external link (for example, from an email or search engine result).
-
Example URL:
www.electronics-high-tech.com.au/en-nz/p/iphone-16
-
Identifier available: the URL contains the product's
slug
(for example,iphone-16) and thelocale
(for example,en-nz
). Here the /p/ identifies that this is a PDP route. -
Query method: you must query the Product Projection Search endpoint filtering by the
slug
andlocale
extracted from the URL path.- Example conceptual filter:
slug(en="iphone-16")
- Example conceptual filter:
Fetch a Product by ID
withId()
method on the Product Projections endpoint allows you to target a specific product using its system-generated ID. Setting staged: false
ensures you retrieve the live, published projection.Code example
Below is a sample code snippet that demonstrates how to fetch a Product by its ID in your Backend-for-Frontend (BFF) layer.
/**
* Fetches a product projection by its unique ID.
* @param id - The system-generated ID of the product.
* @returns The matching product projection.
*/
async fetchProductById(id: string): Promise<ProductProjection> {
try {
const response = (await apiRoot
.productProjections()
.withId({ ID: id }) // Use the withId() method to target specific product
.get({ queryArgs: { staged: false, withTotal: false } }) // Only fetch published products
.execute()) as ClientResponse<ProductProjection>;
if (!response.body) {
throw new NotFoundException(`Product with ID '${id}' not found.`);
}
this.logger.log(
`Fetched product by ID: ${JSON.stringify(response.body)}`,
);
return response.body;
} catch (error) {
this.handleServiceError(error, `ID '${id}'`);
}
}
Key takeaways
withId({ ID: id })
: targets a specific product by its system-generated ID, ensuring you fetch exactly the product you need.queryArgs: { staged: false }
: guarantees that only the live, published product is returned, ensuring up-to-date and customer-visible product data.execute()
: sends the API request and returns the response.- Error handling: to ensure robustness and maintainability, error handling is centralized using a helper method:
this.handleServiceError(error, context)
. This method logs errors with contextual information and rethrows them as meaningful HTTP exceptions (for example,NotFoundException
,InternalServerErrorException
). This pattern ensures that all service methods provide consistent and descriptive error responses.
Execute and observe the response
fetchProductById()
method from your controller (using Express, Node.js, NestJS, etc.), and observe the JSON response. For example, create a GET endpoint that invokes this method, and then use an API client (like Swagger UI or Postman) to call that endpoint. The response should match a structure similar to the sample response below:{
"id": "2bbb247c-9d08-4018-b3ee-07500af7b50b",
"version": 11,
"productType": {
"typeId": "product-type",
"id": "9a8556bb-e155-4f08-ae86-ee1e8ce2bdf2"
},
"name": { "en-US": "Charlie Armchair" },
"description": { "en-US": "A comfortable and stylish armchair..." },
"masterVariant": {
/* ...variant details... */
},
"variants": [
/* ...additional variants... */
]
}
After observing and verifying the response structure against the sample provided, you'll have confidence that your PDP is correctly integrated, reliably fetching live and accurate product data to deliver an exceptional customer experience.
Fetch a Product by Key
withKey()
method on the Product Projections endpoint, enabling developers to retrieve product data using human-friendly product keys. Similar to fetching by ID, setting the staged
parameter to false
ensures that you only fetch the live, published product data displayed to customers.Use case
high-performance-laptop-pro
to find and edit product information. These keys also play a role in internal workflows that contribute to the PDP content and linking, like content management and marketing campaign setup.Code example
Below is a sample code snippet demonstrating how to fetch a product using its key in your Backend-for-Frontend (BFF) layer.
/**
* Fetches a product projection by its key.
* @param key - The human-readable product key.
* @returns The matching product projection.
*/
async fetchProductByKey(key: string): Promise<ProductProjection> {
try {
const response = (await apiRoot
.productProjections()
.withKey({ key }) // Target by product key
.get({ queryArgs: { staged: false } }) // Only fetch published data
.execute()) as ClientResponse<ProductProjection>;
this.logger.log(
`Fetched product by key: ${JSON.stringify(response.body)}`,
);
return response.body;
} catch (error) {
this.handleServiceError(error, `key '${key}'`);
}
}
Key takeaways
withKey({ key })
: allows retrieval of product data using human-friendly keys, making management easier compared to system-generated IDs.queryArgs: { staged: false }
: ensures you only retrieve the published product data, crucial for delivering accurate information to your customers.execute()
: sends the API request and returns the response.
Execute and observe the response
fetchProductByKey()
method from your controller and observe the JSON response. For instance, create a dedicated GET endpoint that calls this method. You can use API clients like Swagger UI or Postman to execute the endpoint call and inspect the response. The response structure should resemble the following sample:{
"id": "2bbb247c-9d08-4018-b3ee-07500af7b50b",
"version": 11,
"productType": {
"typeId": "product-type",
"id": "9a8556bb-e155-4f08-ae86-ee1e8ce2bdf2"
},
"name": { "en-US": "Charlie Armchair" },
"description": { "en-US": "A comfortable and stylish armchair..." },
"masterVariant": {
/* ...variant details... */
},
"variants": [
/* ...additional variants... */
]
}
Fetch a Product by slug
where
clause on the Product Projections endpoint. Setting the staged
parameter to false
ensures you only fetch published, live product data visible to customers. The localeProjection
parameter ensures the product data is returned in the correct language.Use case
/p/smart-speaker
, the application uses the smart-speaker
slug to fetch the product details for display.Code example
Below is a sample code snippet that demonstrates how to fetch a product by its slug in your Backend-for-Frontend (BFF) layer:
/**
* Fetches a product projection using its localized slug.
* @param slug - The human-friendly slug (For example, "charlie-armchair").
* @param locale - The locale (For example, "en-US").
* @returns The first matching product projection.
*/
async fetchProductBySlug(
slug: string,
locale: string,
): Promise<ProductProjection> {
try {
const response = (await apiRoot
.productProjections()
.get({
queryArgs: {
where: `slug(${locale} = "${slug}")`, // Match by localized slug
staged: false, // Fetch only published products
},
})
.execute()) as ClientResponse<ProductProjectionPagedQueryResponse>;
const product = response.body.results?.[0]; // Use the first match
if (!product) {
throw new NotFoundException(`Product with slug '${slug}' not found.`);
}
this.logger.log(`Fetched product by slug: ${JSON.stringify(product)}`);
return product;
} catch (error) {
this.handleServiceError(error, `slug '${slug}'`);
}
}
Key takeaways
where: slug(${locale} = "${slug}")
: uses a locale-specific condition to filter products by slug. This is essential since slugs vary by locale and ensure accurate matches.staged: false
: guarantees that only the current, live product data is returned—perfect for public-facing pages.- No
localeProjection
used: omitting this parameter returns all localized fields (for example,name
,slug
,description
in all configured languages), giving flexibility to the frontend for multi-locale support. - API returns
ProductProjectionPagedQueryResponse
: the response contains aresults
array. Useresults[0]
to access the first matched product. It's common to receive a single match when querying by a unique slug.
Execute and observe the response
fetchProductBySlug()
method from your controller and inspect the JSON response. You can test it via Swagger UI or Postman by invoking the associated endpoint. The response will include all localized fields and should resemble the following:{
"id": "2bbb247c-9d08-4018-b3ee-07500af7b50b",
"version": 11,
"productType": {
"typeId": "product-type",
"id": "9a8556bb-e155-4f08-ae86-ee1e8ce2bdf2"
},
"name": {
"en-US": "Charlie Armchair",
"en-GB": "Charlie Lounge Chair",
"de-DE": "Charlie Sessel"
},
"slug": {
"en-US": "charlie-armchair",
"en-GB": "charlie-lounge-chair",
"de-DE": "charlie-sessel"
},
"description": {
"en-US": "A comfortable and stylish armchair for your living room.",
"en-GB": "A sleek, modern lounge chair to complement your decor.",
"de-DE": "Ein bequemer und stilvoller Sessel für Ihr Wohnzimmer."
},
"masterVariant": {
/* ...variant details... */
},
"variants": [
/* ...additional variants... */
]
}
After confirming that the response contains the full set of localized fields, you’ll ensure your PDP is equipped to serve dynamic, locale-aware content—improving both user experience and SEO across different regions.
Choose the right query method
Let’s summarise what we have covered so that you can easily evaluate when you should choose either of the above three options:
Product ID
- Best for: backend systems, internal tooling, and system-to-system integrations. While primarily used in these contexts, the product ID can also be crucial internally for tasks supporting the functionality and analysis of the PDP.
- Benefits: unique, system-assigned, precise
- Code Example:
apiRoot
.productProjections()
.withId({ ID: 'abc123-def456-ghi789' })
.get({ queryArgs: { staged: false } })
.execute();
Product key
- Best for: catalog management, administrator dashboards, PIM/CMS integrations. Product keys are valuable in these internal systems and can also play a role in workflows that ultimately populate or link to the PDP, such as content management and marketing campaigns.
- Benefits: human-readable, stable, easier to maintain
- Code example:
apiRoot
.productProjections()
.withKey({ key: 'zen-electron-smartphone-pro' })
.get({ queryArgs: { staged: false } })
.execute();
Product slug
- Best for: customer-facing PDPs, localized storefronts, SEO-optimized experiences
- Benefits: intuitive URLs, localization support, better customer experience
- Code example:
apiRoot
.productProjections()
.get({
queryArgs: {
where: 'slug(en-US="charlie-armchair")',
staged: false,
localeProjection: 'en-US',
},
})
.execute();
By selecting the appropriate query method based on your context—whether you're building backend services, managing catalogs, or serving PDPs—you ensure that Zen Electron’s PDP remains performant, intuitive, and aligned with both internal architecture and customer expectations.