docs: added how-to guide for show products (#3828)
* docs: added how-to guide for show products * small fixes * fix links * renamed store directory * fixing links after renaming directory * fix links after renaming directory * fix link * fixed sidebar link * fix tab
This commit is contained in:
@@ -681,4 +681,4 @@ The request returns the following fields:
|
|||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [How to use product categories in a storefront](../store/use-categories.mdx)
|
- [How to use product categories in a storefront](../storefront/use-categories.mdx)
|
||||||
|
|||||||
@@ -93,4 +93,4 @@ Aside from these relations, the `mpath` attribute, which is a [Materialized Path
|
|||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [How to manage product categories using the admin APIs](./admin/manage-categories.mdx)
|
- [How to manage product categories using the admin APIs](./admin/manage-categories.mdx)
|
||||||
- [How to use product categories in a storefront](./store/use-categories.mdx)
|
- [How to use product categories in a storefront](./storefront/use-categories.mdx)
|
||||||
|
|||||||
@@ -42,12 +42,11 @@ Admins can manage unlimited amount of products and their variants. They can mana
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: '#',
|
href: '/modules/products/storefront/show-products',
|
||||||
label: 'Storefront: Show Products',
|
label: 'Storefront: Show Products',
|
||||||
customProps: {
|
customProps: {
|
||||||
icon: Icons['academic-cap-solid'],
|
icon: Icons['academic-cap-solid'],
|
||||||
description: 'Learn how to show products in a storefront.',
|
description: 'Learn how to show products in a storefront.',
|
||||||
isSoon: true,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]} />
|
]} />
|
||||||
@@ -79,7 +78,7 @@ Customers can use this organization to filter products while browsing them.
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: '/modules/products/store/use-categories',
|
href: '/modules/products/storefront/use-categories',
|
||||||
label: 'Storefront: Use Categories',
|
label: 'Storefront: Use Categories',
|
||||||
customProps: {
|
customProps: {
|
||||||
icon: Icons['server-solid'],
|
icon: Icons['server-solid'],
|
||||||
|
|||||||
@@ -0,0 +1,624 @@
|
|||||||
|
---
|
||||||
|
description: 'Learn how to show products in your storefront using the Store REST APIs. This includes listing products, showing the price of a product, and more.'
|
||||||
|
addHowToData: true
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
# How to Show Products on the Storefront
|
||||||
|
|
||||||
|
In this document, you’ll learn how to show products in your storefront using the Store REST APIs.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Using the products store REST APIs, you can display products on your storefront along with their different details.
|
||||||
|
|
||||||
|
### Scenario
|
||||||
|
|
||||||
|
You want to add or use the following storefront functionalities:
|
||||||
|
|
||||||
|
- List products with filters.
|
||||||
|
- Display product prices.
|
||||||
|
- Search products.
|
||||||
|
- Retrieve details of a single product by ID or by handle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Medusa Components
|
||||||
|
|
||||||
|
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||||
|
|
||||||
|
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js starter storefront](../../../starters/nextjs-medusa-starter.mdx).
|
||||||
|
|
||||||
|
### JS Client
|
||||||
|
|
||||||
|
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||||
|
|
||||||
|
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) and have [created an instance of the client](../../../js-client/overview.md#configuration).
|
||||||
|
|
||||||
|
### Medusa React
|
||||||
|
|
||||||
|
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||||
|
|
||||||
|
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## List Products
|
||||||
|
|
||||||
|
You can list available products using the [List Products endpoint](/api/store#tag/Products/operation/GetProducts):
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.list()
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useProducts } from "medusa-react"
|
||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { products, isLoading } = useProducts()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{products && !products.length && <span>No Products</span>}
|
||||||
|
{products && products.length > 0 && (
|
||||||
|
<ul>
|
||||||
|
{products.map((product: Product) => (
|
||||||
|
<li key={product.id}>{product.title}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products`, {
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X GET '<BACKEND_URL>/store/products'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This endpoint does not require any parameters. You can pass it parameters related to pagination, filtering, and more as explained in the [API reference](/api/store#tag/Products/operation/GetProducts).
|
||||||
|
|
||||||
|
The request returns an array of product objects along with [pagination parameters](/api/store#section/Pagination).
|
||||||
|
|
||||||
|
### Filtering Retrieved Products
|
||||||
|
|
||||||
|
The List Products endpoint accepts different query parameters that allow you to filter through retrieved results.
|
||||||
|
|
||||||
|
For example, you can filter products by a category ID:
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.list({
|
||||||
|
category_id: ["cat_123"],
|
||||||
|
})
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useProducts } from "medusa-react"
|
||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { products, isLoading } = useProducts({
|
||||||
|
category_id: ["cat_123"],
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{products && !products.length && <span>No Products</span>}
|
||||||
|
{products && products.length > 0 && (
|
||||||
|
<ul>
|
||||||
|
{products.map((product: Product) => (
|
||||||
|
<li key={product.id}>{product.title}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products?category_id[]=cat_123`, {
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X GET '<BACKEND_URL>/store/products?category_id[]=cat_123'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This will retrieve only products that belong to that category.
|
||||||
|
|
||||||
|
### Product Pricing Parameters
|
||||||
|
|
||||||
|
By default, the prices are retrieved based on the default currency associated with a store. You can use the following query parameters to ensure you are retrieving correct pricing based on the customer’s context:
|
||||||
|
|
||||||
|
- `region_id`: The ID of the customer’s region.
|
||||||
|
- `cart_id`: The ID of the customer’s cart.
|
||||||
|
- `currency_code`: The code of the currency to retrieve prices for.
|
||||||
|
|
||||||
|
It’s recommended to always include the cart and region’s IDs when you’re listing or retrieving a single product’s details, as it’ll show you the correct pricing fields as explained in the next section.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.list({
|
||||||
|
cart_id,
|
||||||
|
region_id,
|
||||||
|
})
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useProducts } from "medusa-react"
|
||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { products, isLoading } = useProducts({
|
||||||
|
cart_id,
|
||||||
|
region_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{products && !products.length && <span>No Products</span>}
|
||||||
|
{products && products.length > 0 && (
|
||||||
|
<ul>
|
||||||
|
{products.map((product: Product) => (
|
||||||
|
<li key={product.id}>{product.title}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
<!-- eslint-disable max-len -->
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products?cart_id=${cartId}®ion_id=${regionId}`, {
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
console.log(products.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X GET '<BACKEND_URL>/store/products?cart_id=<CART_ID>®ion_id=<REGION_ID>'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Display Product Price
|
||||||
|
|
||||||
|
Each product object in the retrieved array has a `variants` array. Each item in the `variants` array is a product variant object.
|
||||||
|
|
||||||
|
Product prices are available for each variant in the product. Each variant has a `prices` array with all the available prices in the context. However, when displaying the variant’s price, you’ll use the following properties inside a variant object:
|
||||||
|
|
||||||
|
- `original_price`: The original price of the product variant.
|
||||||
|
- `calculated_price`: The calculated price, which can be based on prices defined in a price list.
|
||||||
|
- `original_tax`: The tax amount applied to the original price, if any.
|
||||||
|
- `calculated_tax`: The tax amount applied to the calculated price, if any.
|
||||||
|
- `original_price_incl_tax`: The price after applying the tax amount on the original price.
|
||||||
|
- `calculated_price_incl_tax`: The price after applying the tax amount on the calculated price
|
||||||
|
|
||||||
|
Typically, you would display the `calculated_price_incl_tax` as the price of the product variant.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
You must pass one of the [pricing parameters](#product-pricing-parameters) to the request to retrieve these values. Otherwise, their value will be `null`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Prices in Medusa are stored as the price entered by the admin multiplied by a 100. So, to show the correct price, you would need to convert it to a decimal with a method like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const convertToDecimal = (amount: number) => {
|
||||||
|
return Math.floor(amount) / 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- vale docs.Spacing = NO -->
|
||||||
|
|
||||||
|
To display it along with a currency, it’s recommended to use JavaScript’s [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). For example:
|
||||||
|
|
||||||
|
<!-- vale docs.Spacing = YES -->
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Intl.NumberFormat("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "eur",
|
||||||
|
}).format(convertToDecimal(amount))
|
||||||
|
```
|
||||||
|
|
||||||
|
Ideally, you would retrieve the value of the `currency` property from the selected region’s `currency_code` attribute.
|
||||||
|
|
||||||
|
Medusa React provides utility methods such as `formatVariantPrice` that handles this logic for you.
|
||||||
|
|
||||||
|
Here’s an example of how you can calculate the price with and without Medusa React:
|
||||||
|
|
||||||
|
<Tabs groupId="client-preference" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Without Medusa Client" default>
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import Medusa from "@medusajs/medusa-js"
|
||||||
|
|
||||||
|
const medusa = new Medusa({
|
||||||
|
baseUrl: "<YOUR_BACKEND_URL>",
|
||||||
|
maxRetries: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Products() {
|
||||||
|
const [products, setProducts] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
medusa.products.list({
|
||||||
|
// TODO assuming region is already defined somewhere
|
||||||
|
region_id: region.id,
|
||||||
|
})
|
||||||
|
.then(({ products, limit, offset, count }) => {
|
||||||
|
// ignore pagination for sake of example
|
||||||
|
setProducts(products)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const convertToDecimal = (amount) => {
|
||||||
|
return Math.floor(amount) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPrice = (amount) => {
|
||||||
|
return new Intl.NumberFormat("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
// TODO assuming region is already defined somewhere
|
||||||
|
currency: region.currency_code,
|
||||||
|
}).format(convertToDecimal(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{products.map((product) => (
|
||||||
|
<>
|
||||||
|
{product.variants.map((variant) => (
|
||||||
|
<li key={variant.id}>{
|
||||||
|
formatPrice(variant.calculated_price_incl_tax)
|
||||||
|
}</li>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="With Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { formatVariantPrice, useProducts } from "medusa-react"
|
||||||
|
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { products, isLoading } = useProducts({
|
||||||
|
region_id: region.id, // assuming already defined somewhere
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{products && !products.length && (
|
||||||
|
<span>No Products</span>
|
||||||
|
)}
|
||||||
|
{products && products.length > 0 && (
|
||||||
|
<ul>
|
||||||
|
{products.map((product: Product) => (
|
||||||
|
<>
|
||||||
|
{product.variants.map(
|
||||||
|
(variant: ProductVariant) => (
|
||||||
|
<li key={variant.id}>
|
||||||
|
{formatVariantPrice({
|
||||||
|
variant,
|
||||||
|
// assuming already defined somewhere
|
||||||
|
region,
|
||||||
|
})}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search Products
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
The Search functionality requires either installing a [search plugin](../../../plugins/search/index.mdx) or creating a search service.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
You can search products using the [Search Products endpoint](/api/store#tag/Products/operation/PostProductsSearch):
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.search({
|
||||||
|
q: "Shirt",
|
||||||
|
})
|
||||||
|
.then(({ hits }) => {
|
||||||
|
console.log(hits.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products/search?q=Shirt`, {
|
||||||
|
credentials: "include",
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ hits }) => {
|
||||||
|
console.log(hits.length)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X POST '<BACKEND_URL>/store/products/search?q=Shirt'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This endpoint requires the query parameter `q` being the term to search products for. The search plugin or service you’re using determine how `q` will be used to search the products. It also accepts pagination parameters as explained in the [API reference](/api/store#tag/Products/operation/PostProductsSearch).
|
||||||
|
|
||||||
|
The request returns a `hits` array holding the result items. The structure of the items depends on the plugin you’re using.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Retrieve a Product by ID
|
||||||
|
|
||||||
|
You can retrieve the details of a single product by its ID using the [Get a Product endpoint](/api/store#tag/Products/operation/GetProductsProduct):
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.retrieve(productId)
|
||||||
|
.then(({ product }) => {
|
||||||
|
console.log(product.id)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useProduct } from "medusa-react"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { product, isLoading } = useProduct(productId)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{product && <span>{product.title}</span>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products/${productId}`, {
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ product }) => {
|
||||||
|
console.log(product.id)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X GET '<BACKEND_URL>/store/products/<PRODUCT_ID>'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This endpoint requires the product’s ID to be passed as a path parameter. You can also pass query parameters such as `cart_id` and `region_id` which are relevant for pricing as explained in the [Product Pricing Parameters section](#product-pricing-parameters). You can check the full list of accepted parameters in the [API reference](/api/store#tag/Products/operation/GetProductsProduct).
|
||||||
|
|
||||||
|
The request returns a product object. You can display its price as explained in the [Display Product Price](#display-product-price) section.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Retrieve Product by Handle
|
||||||
|
|
||||||
|
On the storefront, you may use the handle of a product as its page’s path. For example, instead of displaying the product’s details on the path `/products/prod_123`, you can display it on the path `/products/shirt`, where `shirt` is the handle of the product. This type of URL is human-readable and is good for Search Engine Optimization (SEO)
|
||||||
|
|
||||||
|
You can retrieve the details of a product by its handle by sending a request to the List Products endpoint, passing the `handle` as a filter:
|
||||||
|
|
||||||
|
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||||
|
<TabItem value="client" label="Medusa JS Client" default>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
medusa.products.list({
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
.then(({ products }) => {
|
||||||
|
if (!products.length) {
|
||||||
|
// product does not exist
|
||||||
|
}
|
||||||
|
const product = products[0]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="medusa-react" label="Medusa React">
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useProducts } from "medusa-react"
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const { products, isLoading } = useProducts({
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading && <span>Loading...</span>}
|
||||||
|
{products && !products.length && (
|
||||||
|
<span>Product does not exist</span>
|
||||||
|
)}
|
||||||
|
{products && products.length > 0 && products[0].title}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="fetch" label="Fetch API">
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fetch(`<BACKEND_URL>/store/products?handle=${handle}`, {
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ products }) => {
|
||||||
|
if (!products.length) {
|
||||||
|
// product does not exist
|
||||||
|
}
|
||||||
|
const product = products[0]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="curl" label="cURL">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L -X GET '<BACKEND_URL>/store/products?handle=<HANDLE>'
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
As the `handle` of each product is unique, when you pass the handle as a filter you’ll either:
|
||||||
|
|
||||||
|
- receive an empty `products` array, meaning the product doesn’t exist;
|
||||||
|
- or you’ll receive a `products` array with one item being the product you’re looking for. In this case, you can access the product at index `0`.
|
||||||
|
|
||||||
|
As explained earlier, make sure to pass the [product pricing parameters](#product-pricing-parameters) to [display the product's price](#display-product-price)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [How to use regions in a storefront](../../regions-and-currencies/storefront/use-regions.mdx)
|
||||||
|
- [How to implement cart functionality](../../carts-and-checkout/storefront/implement-cart.mdx)
|
||||||
@@ -633,16 +633,13 @@ module.exports = {
|
|||||||
label: "Admin: Import Products",
|
label: "Admin: Import Products",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "doc",
|
||||||
href: "#",
|
id: "modules/products/storefront/show-products",
|
||||||
label: "Storefront: Show Products",
|
label: "Storefront: Show Products",
|
||||||
customProps: {
|
|
||||||
sidebar_is_soon: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: "doc",
|
||||||
id: "modules/products/store/use-categories",
|
id: "modules/products/storefront/use-categories",
|
||||||
label: "Storefront: Use Categories",
|
label: "Storefront: Use Categories",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user