Show Products in the Storefront

List, paginate, filter, and sort catalog products.

On this page

In this guide, you'll learn how to list, paginate, and filter products in your storefront.

For Next.js App Router, call the Store API from src/lib/data/products.ts with "use server". Pass storeCode first, resolve region_id from the URL country, and request calculated prices only when a region exists. See Build a Next.js Storefront.

List Products

To retrieve products, send a request to the List Products API route:

Tip: Learn how to install and configure the JS SDK in the Getting started guide.

ts

tsCopy
"use server"
const CATALOG_FIELDS =
"id,title,handle,thumbnail,*variants,+variants.inventory_quantity,*variants.options,*options,+images,+metadata,+tags"
const PRICED_FIELDS =
"id,title,handle,thumbnail,*variants,*variants.calculated_price,+variants.inventory_quantity,*variants.options,*options,+images,+metadata,+tags"
export const listProducts = async ({
storeCode,
countryCode,
queryParams,
}: {
storeCode: string
countryCode?: string
queryParams?: HttpTypes.StoreProductListParams
}) => {
const region = countryCode
? await getRegion(storeCode, countryCode)
: null
const regionId = region?.id
const fields = regionId ? PRICED_FIELDS : CATALOG_FIELDS
const { products, count } = await sdk.store.product.list(storeCode, {
...queryParams,
...(regionId ? { region_id: regionId } : {}),
fields,
})
return { products, count }
}

Never request calculated_price without a region_id. Use this from a server component (app/[countryCode]/(store)/shop/page.tsx), not from a client useEffect.

React (SPA)

tsx

tsxCopy
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
import { HttpTypes } from "@aiminaabeejs/types"
import { sdk } from "@/lib/sdk"
export default function Products() {
const [loading, setLoading] = useState(true)
const [products, setProducts] = useState<
HttpTypes.StoreProduct[]
>([])
useEffect(() => {
if (!loading) {
return
}
sdk.store.product.list("us")
.then(({ products: dataProducts }) => {
setProducts(dataProducts)
setLoading(false)
})
}, [loading])
return (
<div>
{loading && <span>Loading...</span>}
{!loading && products.length === 0 && <span>No products found.</span>}
{!loading && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
</div>
)
}

JS SDK

ts

tsCopy
sdk.store.product.list("us")
.then(({ products: dataProducts }) => {
setProducts(dataProducts)
setLoading(false)
})

The response has a products field, which is an array of products.


Paginate Products

To paginate products, pass the following query parameters:

  • limit: The number of products to return in the request.
  • offset: The number of products to skip before the returned products. You can calculate this by multiplying the current page with the limit.

The response object returns a count field, which is the total count of products. Use it to determine whether there are more products that can be loaded.

For example:

tsx

tsxCopy
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
import { HttpTypes } from "@aiminaabeejs/types"
import { sdk } from "@/lib/sdk"
export default function Products() {
const [loading, setLoading] = useState(true)
const [products, setProducts] = useState<
HttpTypes.StoreProduct[]
>([])
const limit = 20
const [currentPage, setCurrentPage] = useState(1)
const [hasMorePages, setHasMorePages] = useState(false)
useEffect(() => {
if (!loading) {
return
}
const offset = (currentPage - 1) * limit
sdk.store.product.list("us", {
limit,
offset,
})
.then(({ products: dataProducts, count }) => {
setProducts((prev) => {
if (prev.length > offset) {
// products already added because the same request has already been sent
return prev
}
return [
...prev,
...dataProducts,
]
})
setHasMorePages(count > limit * currentPage)
setLoading(false)
})
}, [loading])
return (
<div>
{loading && <span>Loading...</span>}
{!loading && products.length === 0 && <span>No products found.</span>}
{!loading && products.length > 0 && (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
)}
{!loading && hasMorePages && (
<button
onClick={() => {
setCurrentPage((prev) => prev + 1)
setLoading(true)
}}
disabled={loading}
>
Load More
</button>
)}
</div>
)
}

In the example above, you add a useEffect hook that runs whenever the loading state changes. This hook fetches the products, passing the limit and offset parameters to retrieve the paginated products.

You then show a button to load more products if there are more pages.


Filter Products

The List Products API route accepts query parameters to filter products by title, category, handle, and more.

Refer to the API reference for the full list of accepted query parameters.

Filter by Options

This feature is available in the current Aiminaabee API.

The List Products API route supports filtering products by options and their values, such as size or color. This is useful to add filtering functionality to your storefront.

Learn more about product options and how to filter by them in the Product Options guide.

Filter by Keyword

For example, to filter products by a keyword:

ts

tsCopy
sdk.store.product.list("us", {
q: "Shirt",
})
.then(({ products: dataProducts, count }) => {
// TODO set products...
})

The q parameter is used to filter a product's searchable fields, such as its title or description, by a keyword.

The result will be products that match the keyword in their title or description.


Sort Products

To sort products by a field, use the order query parameter. Its value is a comma-separated list of fields to sort by, and each field is optionally prefixed by - to indicate descending order.

For example, to sort products by title in descending order:

ts

tsCopy
sdk.store.product.list("us", {
order: "-title",
})
.then(({ products: dataProducts, count }) => {
// TODO set products...
})

The result will be products sorted by title in descending order.


Retrieve Translations for Products

Prerequisites

  • Translation Module Configured

By default, Aiminaabee returns the product's original content (such as title and description).

If you support localization in your storefront, you can set the locale to retrieve product information with based on the customer's preferred language.

You can set the locale using one of the following methods:

  • Use the JS SDK's setLocale method. The JS SDK will automatically include the locale in subsequent requests.
  • Pass the locale query parameter to the List Products API route.
  • Set the x-aiminaabee-locale header in the API request to the List Products API route.

For example:

Using JS SDK

ts

tsCopy
sdk.setLocale("fr-FR")
sdk.store.product.list("us")
.then(({ products: dataProducts, count }) => {
// TODO set products...
})

Using Query Parameter

bash

bashCopy
curl "http://localhost:3000/store/products?locale=fr-FR" \
-H 'x-publishable-api-key: {your_publishable_api_key}'

Using Header

bash

bashCopy
curl "http://localhost:3000/store/products" \
-H 'x-publishable-api-key: {your_publishable_api_key}' \
-H "x-aiminaabee-locale: fr-FR"

The returned products will have the same structure as described in the products schema, but their fields like title and description will be in the specified locale:

json

jsonCopy
{
"products": [
{
"id": "prod_123",
"title": "Chemise Exemple",
"description": "Ceci est une description en français.",
// other product fields...
}
]
}

If translations aren't available for the selected locale, or no locale is selected, the product's original content is returned.

Retrieve in Server-Side Environments

For server-side environments (such as server components or server actions in Next.js), you can set the locale using cookies to persist the selected locale across requests.

Learn more in the Storefront Localization guide.