> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Lab - Create a Catalog Flow in Postman

Plan: Developer Foundations

Lesson 16 of 28 · 45 min

## Introduction

In this lab, you'll build on the Postman entities created in the previous exercise to create a progressive catalog workflow, querying for lists of categories and products and then using the results to query for more specific information.

You may choose to use an API client with similar features and adapt the following instructions, but the instructions themselves assume the use of Postman.

Certain catalog data must be present in your BigCommerce store in order for the exercise queries to succeed. We'll detail the requirements below.

### In this lab, you will:

* Create a series of queries to find a valid top-level category and retrieve its information
* Create a series of queries to paginate through the store's products, find a product with variant options, and retrieve its information

### Prerequisites

* A BigCommerce [sandbox store](/developer/docs/overview/sandboxes) or [trial store](https://www.bigcommerce.com/essentials/), or a full production store
* [Postman](https://www.postman.com/)
* An existing Postman environment and collection as configured in previous labs
* At least one visible top-level category with at least one visible child category
* At least 6 products

### Category prerequisites

Your BigCommerce store must contain a top-level category (which can be different than the category with child categories) that:

* Is visible
* Has products assigned

### Product prerequisites

Your BigCommerce store must contain at least one product that:

* Is physical
* Is in stock and purchasable
* Has at least one in-stock variant option
* Has no required modifier options

## Postman Recap

As a reminder, your work in previous labs should have resulted in the following Postman configuration, which will be important for the following exercise steps:

* An environment with variables set for `v3_token`, `store_hash`, `storefront_channel_id` and `private_storefront_token`
* A collection with Bearer Token authentication using the value of `private_storefront_token`
* A "REST Create Private Token (server-to-server)" request that will populate `private_storefront_token` when run

## Create a Category Tree Request

### Step 1: Create the Request

1. **Create** a new HTTP request and **Save** it in your collection with the name "GQL Get Category Tree."
2. In the Settings tab, **Set** "Disable cookie jar" to "On." Since we are using a private token for server-to-server requests, we must avoid sending any cookies to ensure BigCommerce does not think our request is being sent from a browser context.
3. In the Authorization tab, make sure Type is **Set** to "Inherit auth from parent." This will utilize the authorization method already set up in the collection.
4. **Set** "POST" as the request method.
5. **Enter** the following exact value as the URL for the request:

```http showLineNumbers={false}
https://store-{{store_hash}}-{{storefront_channel_id}}.mybigcommerce.com/graphql
```

6. In the Headers tab, use the Presets drop-down to **Manage Presets**, then **Add** a header preset with the following values:

| Header       | Value            |
| ------------ | ---------------- |
| Accept       | application/json |
| Content-Type | application/json |

7. **Select** your newly created header preset to populate headers.
8. In the Body tab, **Select** "GraphQL" as the body type and **Enter** the following query:

```graphql showLineNumbers={false}
query {
  site {
    categoryTree {
      ...categoryFields
      children {
        ...categoryFields
        children {
          ...categoryFields
        }
      }
    }
  }
}

fragment categoryFields on CategoryTreeItem {
  entityId
  name
  path
  productCount
}
```

We are querying for categories and their children, starting at the root of the store and querying three levels deep. Since the fields we are querying for each hierarchy level are the same, we are using a GraphQL fragment to specify them.

9. **Save** the request, make sure you have selected your BigCommerce environment in the environment drop-down, then **Send** the request and observe the results.

### Step 2: Add Tests

The following tests will verify that the Category Tree request returns at least one top-level category with products.

1. In the Scripts tab, **Enter** the following "Post-response" value.

```js showLineNumbers={false}
pm.test("Response is not an error", () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test("Response is JSON with data", () => {
    pm.response.to.have.jsonBody("data");
});

const topCategories = pm.response.json().data?.site?.categoryTree;
pm.test("Response includes top-level categories", () => {
    pm.expect(topCategories).to.be.a('array');
    pm.expect(topCategories.length).to.be.above(0);
});

const firstCategoryWithProducts = topCategories.find(category => {
    return category.productCount > 0;
});
pm.test("Response includes at least one top-level category with products", () => {
    pm.expect(firstCategoryWithProducts).not.to.be.undefined;
    pm.expect(firstCategoryWithProducts.entityId).to.be.a("number");
});
```

2. **Send** the request again and **Verify** all tests pass in the Test Results tab of the response panel.

### Step 3: Store a Category ID

We're now going to store the ID of one of the top-level categories in a collection variable, which will be used in our next request. With this flow, you can run the requests in sequence rather than needing to manually find and enter a category ID for the individual category request.

> **Info**
>
> Note that we're storing this value in a collection variable, rather than in an environment variable as we've done before. We've used environment variables for values that are likely to be relevant in numerous contexts when connecting to a particular environment, while values specific to the workflow we're implementing are stored on the collection.

1. In the Scripts tab, **add** the following after the original test code.

```js showLineNumbers={false}
pm.collectionVariables.unset("category_id");
if (firstCategoryWithProducts) {
    pm.collectionVariables.set("category_id", firstCategoryWithProducts.entityId);
}
```

2. **Send** the request again.
3. **Verify** that the new variable `category_id` is populated in the Variables tab of the collection.

## Create a Category Request

### Step 1: Create the Request

Now we'll use the category ID we found in the previous request to query information about a specific category.

1. **Create** a new HTTP request and **Save** it in your collection with the name "GQL Get Category."
2. **Set** the same configuration that was used for the previous request, including the HTTP method, URL, Authorization, Headers (using your previously created preset), Body type and "Disable cookie jar" setting.
3. **Enter** the following query:

```graphql showLineNumbers={false}
query GetCategory($categoryId: Int!) {
    site {
        category(
            entityId: $categoryId
        ) {
            name
            path
            defaultImage {
                url(width: 500)
            }
            description
        }
    }
}
```

4. **Enter** the following in the GraphQL Variables section of the Body tab:

```json showLineNumbers={false}
{
    "categoryId": {{category_id}}
}
```

We've used a GraphQL variable in the query to specify a category ID, and now we've used our Postman environment variable to populate that value in the GraphQL variables list.

5. **Send** the request and observe the results.

### Step 2: Add Tests

1. **Enter** the following "Post-response" value in the Scripts tab:

```js showLineNumbers={false}
pm.test("Response is not an error", () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test("Response is JSON with data", () => {
    pm.response.to.have.jsonBody("data");
});

pm.test("Response includes category", () => {
    pm.expect(pm.response.json().data?.site?.category?.name).to.be.a("string");
});
```

These tests will verify we successfully received the category in the response.

This time around, we don't have any important response values to store.

2. **Send** the request again and **verify** that tests pass.

## Create a Paginated Products Request

We'll now create not only a query for the store's products, but one which can be run multiple times in sequence to page through results.

### Step 1: Create the Request

1. **Create** a new HTTP request and **Save** it in your collection with the name "GQL Get Paginated Products".
2. **Set** the same configuration that was used for the previous request, including the HTTP method, URL, Authorization, Headers, Body type and "Disable cookie jar" setting.
3. **Enter** the following query:

```graphql showLineNumbers={false}
query GetProducts(
    $lastProductCursor: String
) {
    site {
        products(
            after: $lastProductCursor,
            first: 5
        ) {
            pageInfo {
                hasNextPage
                endCursor
            }
            collectionInfo {
                totalItems
            }
            edges {
                cursor
                node {
                    entityId
                    sku
                    name
                    type
                    availabilityV2 {
                        status
                    }
                    inventory {
                        isInStock
                    }
                    variants(isPurchasable: true) {
                        edges {
                            node {
                                entityId
                                inventory {
                                    isInStock
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```

We're querying for basic product information but also information about the products' variants. We'll ultimately be using this information to find a product with at least one variant.

4. **Enter** the following in the GraphQL Variables section:

```json showLineNumbers={false}
{
    "lastProductCursor": "{{last_product_cursor}}"
}
```

Notice that we're passing `last_product_cursor` to the `after` argument in the query, which controls where in the cursor list we want the query results to start. But we currently have no such Postman variable in our collection or environment.

When the query is run with no such variable, we will get the first 5 results.

5. **Send** the request and observe the results.

### Step 2: Add Tests

Let's add tests to make sure the query returns products, and at least one of those products is physical, is available, and has at least one variant.

1. **Add** the following "Post-response" value in the Scripts tab.

```js showLineNumbers={false}
pm.test("Response is not an error", () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test("Response is JSON with data", () => {
    pm.response.to.have.jsonBody("data");
});

const products = pm.response.json().data?.site?.products?.edges ?? [];
pm.test("Response includes at least one product", () => {
    pm.expect(products.length).to.be.above(0);
});

const firstValidProduct = products.map(product => product.node)
    .find(product => {
        if (product.availabilityV2?.status !== "Available"
            || product.type !== "Physical"
            || product.inventory?.isInStock !== true) {
                return false;
            }

        const variants = product.variants?.edges ?? [];
        const firstValidVariant = variants.map(variant => variant.node)
            .find(variant => {
                return variant.inventory?.isInStock;
            });

        if (!firstValidVariant) {
            return false;
        }

        return product;
    });

pm.test("Found a saleable physical product with a variant", () => {
    pm.expect(firstValidProduct?.entityId).to.be.a("number");
});
```

2. **Send** the request and **Verify** that tests pass.

If you don't have a product with variants in the first 5 results of your store's products, you'll see that the final test fails, even though this doesn't really constitute an unexpected response. That's okay for now, as this test will be our indicator for when we've finally found such a product once we're able to run through multiple pages of results.

### Step 3: Store the Product Cursor

In order to page through results by running this request multiple times, we must capture the ending cursor of the current page.

1. In the Scripts tab, **Add** the following after the existing code:

```js showLineNumbers={false}
if (pm.response.json().data?.site?.products?.pageInfo?.hasNextPage === true) {
    pm.collectionVariables.set(
      "last_product_cursor",
      pm.response.json().data?.site?.products?.pageInfo?.endCursor
    );
}
```

2. **Send** the request. If you have 6 or more visible products on your store, there should be results past the end "cursor" of this initial request, and `last_product_cursor` should now be populated in your collection variables.
3. **Send** the request again. You should see a different set of results, thanks to the cursor stored in the environment variable. If you have enough products to support even more results pages, you can continue to send the request to see each subsequent page.
4. In your collection's Variables tab, **Reset** or **Delete** the `last_product_cursor` variable in order to "start over" the next time you send the request.

### Step 4: Store a Product ID

Our last task with the paginated products request is to find and store the ID of the first product with variants that is found.

1. **Modify** the code in the Scripts tab as shown:

```js {8,23,32-33} showLineNumbers={false}
...

const products = pm.response.json().data?.site?.products?.edges ?? [];
pm.test("Response includes at least one product", () => {
    ...
});

pm.collectionVariables.unset("variant_id");

const firstValidProduct = products.map(product => product.node)
    .find(product => {
        ...

        const firstValidVariant = variants.map(variant => variant.node)
            .find(variant => {
                return variant.inventory?.isInStock;
            });

        if (!firstValidVariant) {
            return false;
        }

        pm.collectionVariables.set("variant_id", firstValidVariant.entityId);

        return product;
    });

pm.test("Found a saleable physical product with a variant", () => {
    ...
});

pm.collectionVariables.unset("product_id");
pm.collectionVariables.set("product_id", firstValidProduct?.entityId);

...
```

> **Info**
>
> Why variant ID?
>
> We are storing the first variant ID as well as the product ID in a collection variable, even though we don't need this for our next request. This will be useful for a later lab.

2. **Send** the request.

We've now created a workflow by which you can clear out the value of the `last_product_cursor` variable on your collection, then send the "Get Paginated Products" request multiple times if necessary until all tests pass. By that point, a product ID and variant ID will have been captured.

3. **Send** the request again, if necessary, until all tests pass. **Verify** that `product_id` and `variant_id` variables are populated in the collection.

## Create a Product Request

### Step 1: Create the Request

1. **Create** a new HTTP request and **Save** it in your collection with the name "GQL Get Product".
2. **Set** the same configuration that was used for the previous request, including the HTTP method, URL, Authorization, Headers, Body type and "Disable cookie jar" setting.
3. **Enter** the following query:

```graphql showLineNumbers={false}
query GetProduct(
    $productId: Int
) {
    site {
        product(
            entityId: $productId
        ) {
            sku
            name
            description
            prices {
                price {
                    currencyCode
                    value
                }
            }
            defaultImage {
                url(width: 500)
            }
            productOptions {
                edges {
                    node {
                        entityId
                        displayName
                        __typename
                        isRequired
                        isVariantOption
                        ... on CheckboxOption {
                            label
                        }
                        ... on MultipleChoiceOption {
                            displayStyle
                            values {
                                edges {
                                    node {
                                        entityId
                                        label
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```

This query will fetch some of the finer details of the product, including the price and default image URL. Of particular note is the information fetched about product options: Since we know we'll be querying for a product with variants, these details will demonstrate how variant information shows up in the available product options.

4. **Enter** the following in the GraphQL Variables section:

```json showLineNumbers={false}
{
    "productId": {{product_id}}
}
```

> **Info**
>
> Before sending this request, make sure you have used the "Get Paginated Products" request to capture a product ID.

5. **Send** the request and observe the results.

### Step 2: Add Tests

1. **Enter** the following "Post-response" value in the Scripts tab:

```js showLineNumbers={false}
pm.test("Response is not an error", () => {
    pm.response.to.not.be.error;
    pm.response.to.not.have.jsonBody("errors");
});
pm.test("Response is JSON with data", () => {
    pm.response.to.have.jsonBody("data");
});
pm.test("Response includes a product", () => {
    pm.expect(pm.response.json().data?.site?.product?.sku).to.be.a("string");
});
```

2. **Send** the request and **Verify** all tests pass.