Fetch Product data

Elevate, May 20-22-2025, Miami Beach, Florida

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.

  1. 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.
  2. 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.
When a user navigates to a PDP, your application needs to fetch the corresponding Product Projection to display the relevant details. The optimal way to query the Product Projection API depends on how the user arrived at the PDP:

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 the locale (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 and locale extracted from the URL path.
    • Example conceptual filter: slug(en="iphone-16")

Scenario 2: Internal navigation

  • Context: a user clicks on a link within your website or application to reach the PDP.
  • Example sources: Product listing pages, category pages, search results grids, CMS components linking to products, cross-sell carousels.
  • Identifiers potentially available: when rendering the source (for example, the product card on a product listing page), you likely already fetched some product data. This data often includes the product's unique ID, key, or slug. This gives you three different unique Product identifiers that can be used to query the Product Projection.
  • Query methods:
    • By ID: preferred for direct lookup. If the unique ID is easily passed from the source component (for example, product card link), query by ID for optimal performance.
    • By key: alternative for direct lookup. If you use business-defined keys and the key is available from the source, query by key. Also highly performant, slightly less preferred over ID.
    • By slug: use when ID/key are unavailable or not convenient to use. Query using the slug and locale. Same as Scenario 1.

In summary:

  • For URL-based access, rely on querying by slug (and locale).
  • For internal navigation, prefer querying by ID over key if that information is easily passed from the originating element (like a product card or search result), as it provides a direct lookup. If not readily available, fall back to using the slug and locale.

This approach ensures you can reliably fetch the correct Product Projection regardless of the user's navigation path while leveraging the most efficient query method based on the available context.

Fetch a Product by ID

The 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

Call the 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

The TypeScript SDK provides the 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

Useful in catalog management interfaces, administrator dashboards, or PIM/CMS systems where human-readable keys improve usability. Imagine a product manager in Zen Electron's internal dashboard using a memorable key like 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

Invoke the 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

The TypeScript SDK allows you to query products by slug using the 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

Ideal for customer-facing storefronts and PDPs where SEO-friendly and readable URLs are important. When a customer clicks on a product link in the Zen Electron storefront with a URL like /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 a results array. Use results[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

Call the 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.

Test your knowledge