Use Product Options in Storefront

In this guide, you'll learn how to list and retrieve product options in your storefront. You'll also learn how to use product options for filtering products.

Note: This feature is available since Medusa v2.16.0.

What are Product Options?#

Product options are attributes like size or color that define the different variants of a product. For example, a t-shirt product may have a "Size" option with values like "S", "M", and "L", and a "Color" option with values like "Red", "Blue", and "Green".

Product options are useful for:

  • Displaying filtering options in your storefront
  • Allowing customers to select product variants based on their preferences
  • Organizing product variants by their attributes

List Product Options#

To retrieve a list of product options, send a request to the List Product Options API route:

Tip: Learn how to install and configure the JS SDK in the JS SDK documentation.

The response has a product_options field, which is an array of product options. Each product option has a values field, which is an array of the option's values.


Paginate Product Options#

To paginate product options, pass the following query parameters:

  • limit: The number of product options to return in the request.
  • offset: The number of product options to skip before the returned options. 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 product options. Use it to determine whether there are more options that can be loaded.

For example:

Code
1"use client" // include with Next.js 13+2
3import { useEffect, useState } from "react"4import { HttpTypes } from "@medusajs/types"5import { sdk } from "@/lib/sdk"6
7export default function ProductOptions() {8  const [loading, setLoading] = useState(true)9  const [options, setOptions] = useState<10    HttpTypes.StoreProductOption[]11  >([])12  const limit = 2013  const [currentPage, setCurrentPage] = useState(1)14  const [hasMorePages, setHasMorePages] = useState(false)15
16  useEffect(() => {17    if (!loading) {18      return 19    }20
21    const offset = (currentPage - 1) * limit22
23    sdk.store.productOption.list({24      limit,25      offset,26    })27    .then(({ product_options, count }) => {28      setOptions((prev) => {29        if (prev.length > offset) {30          // options already added because the same request has already been sent31          return prev32        }33        return [34          ...prev,35          ...product_options,36        ]37      })38      setHasMorePages(count > limit * currentPage)39      setLoading(false)40    })41  }, [loading])42
43  return (44    <div>45      {loading && <span>Loading...</span>}46      {!loading && options.length === 0 && <span>No options found.</span>}47      {!loading && options.length > 0 && (48        <ul>49          {options.map((option) => (50            <li key={option.id}>51              {option.title}52              <ul>53                {option.values?.map((value) => (54                  <li key={value.id}>{value.value}</li>55                ))}56              </ul>57            </li>58          ))}59        </ul>60      )}61      {!loading && hasMorePages && (62        <button63          onClick={() => {64            setCurrentPage((prev) => prev + 1)65            setLoading(true)66          }}67          disabled={loading}68        >69          Load More70        </button>71      )}72    </div>73  )74}

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

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


Retrieve a Product Option#

To retrieve a product option by its ID, send a request to the Retrieve Product Option API route:

The response has a product_option field, which is a product option object.


Use Product Options for Filtering#

Product options are commonly used to add filtering functionality to your storefront. You can display the available options and their values, then allow customers to filter products by selecting specific option values.

For example, you can create a ProductFilters component that retrieves options and allows selecting filters:

Code
1"use client" // include with Next.js 13+2
3import { useEffect, useState } from "react"4import { HttpTypes } from "@medusajs/types"5import { sdk } from "@/lib/sdk"6
7type ProductFiltersProps = {8  selectedValues: string[]9  setSelectedValues: (values: string[]) => void10}11
12export default function ProductFilters({13  selectedValues,14  setSelectedValues,15}: ProductFiltersProps) {16  const [isLoading, setIsLoading] = useState(true)17  const [options, setOptions] = useState<HttpTypes.StoreProductOption[]>([])18
19  useEffect(() => {20    if (!isLoading) {21      return 22    }23
24    sdk.store.productOption.list()25    .then(({ product_options }) => {26      setOptions(product_options)27      setIsLoading(false)28    })29  }, [isLoading])30
31  return (32    <div>33      {isLoading && <span>Loading...</span>}34      {options.map((option) => (35        <div key={option.id}>36          <h4>{option.title}</h4>37          <ul>38            {option.values?.map((value) => (39              <li key={value.id}>40                <input41                  type="checkbox"42                  checked={selectedValues.includes(value.value)}43                  onChange={(e) => {44                    if (e.target.checked) {45                      // add value46                      setSelectedValues([47                        ...selectedValues,48                        value.value,49                      ])50                    } else {51                      // remove value52                      setSelectedValues(53                        selectedValues.filter(54                          (v) => v !== value.value55                        )56                      )57                    }58                  }}59                />60                <label>{value.value}</label>61              </li>62            ))}63          </ul>64        </div>65      ))}66    </div>67  )68}

The component receives the currently selected option values and a function to update them. When a user selects a filter, such as "Blue" for the "Color" option, the selected option values are updated.

Then, in the parent component that lists products, you can pass the selected option values to the option_value_id query parameter of the List Products API route:

Code
1sdk.store.product.list({2  limit,3  offset,4  // a string or array of strings indicating5  // option value IDs to filter by6  option_value_id: selectedOptionValues,7})8.then(({ products, count }) => {9  // TODO set products...10})

The returned products are filtered to only include those whose variants have the selected option values.

Note: To filter variants of the products by option values, use the List Product Variants API route with the option_value_id query parameter.

Filter by Option ID#

Alternatively, you can filter products by the options they have using the option_id query parameter. This allows filtering products that have a specific option, such as "Size", regardless of the option value.

For example:

Code
1sdk.store.product.list({2  limit,3  offset,4  // a string or array of strings indicating5  // option IDs to filter by6  option_id: selectedOptionIds,7})8.then(({ products, count }) => {9  // TODO set products...10})

This returns all products that have the specified options, regardless of the option values.

Was this page helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Medusa MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break