diff --git a/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx b/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
index 1f2390bafe..9758b5c560 100644
--- a/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
@@ -1,4 +1,4 @@
-import { Prerequisites, CardList } from "docs-ui"
+import { Prerequisites, CardList, Table } from "docs-ui"
export const metadata = {
title: `${pageNumber} Create a Plugin`,
@@ -65,18 +65,148 @@ For example:
### Package Keywords
-In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
+Medusa scrapes NPM for a list of plugins that integrate third-party services, to later showcase them on the Medusa website. If you want your plugin to appear in that listing, make sure to add the `medusa-v2` and `medusa-plugin-integration` keywords to the `keywords` field in `package.json`.
```json title="package.json"
{
"keywords": [
- "medusa-plugin",
+ "medusa-plugin-integration",
"medusa-v2"
],
// ...
}
```
+In addition, make sure to use one of the following keywords based on your integration type:
+
+
+
### Package Dependencies
@@ -459,6 +589,12 @@ To learn how to create module providers, refer to the following guides:
## 5. Publish Plugin to NPM
+
+
+Make sure to add the keywords mentioned in the [Package Keywords](#package-keywords) section in your plugin's `package.json` file.
+
+
+
Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
```bash
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index 8a6b5547b9..80795ef7ba 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -103,7 +103,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/data-models/check-constraints/page.mdx": "2024-12-06T14:34:50.384Z",
"app/learn/fundamentals/module-links/link/page.mdx": "2025-04-07T08:03:14.513Z",
"app/learn/fundamentals/workflows/store-executions/page.mdx": "2025-02-11T15:56:03.835Z",
- "app/learn/fundamentals/plugins/create/page.mdx": "2025-04-07T14:50:53.866Z",
+ "app/learn/fundamentals/plugins/create/page.mdx": "2025-04-11T10:39:31.589Z",
"app/learn/fundamentals/plugins/page.mdx": "2025-01-22T10:14:10.433Z",
"app/learn/customization/reuse-customizations/page.mdx": "2025-01-22T10:01:57.665Z",
"app/learn/update/page.mdx": "2025-01-27T08:45:19.030Z",
diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt
index 9414116e14..7c23ff9864 100644
--- a/www/apps/book/public/llms-full.txt
+++ b/www/apps/book/public/llms-full.txt
@@ -1451,52 +1451,6 @@ In the next chapters, you'll continue with the brands example to:
- Add a new page in the dashboard that shows all brands in the store.
-# Extend Core Commerce Features
-
-In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
-
-In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
-
-Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
-
-- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
-- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
-- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
-
-***
-
-## Next Chapters: Link Brands to Products Example
-
-The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
-
-- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
-- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
-- Retrieve a product's associated brand's details.
-
-
-# Integrate Third-Party Systems
-
-Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.
-
-Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.
-
-In Medusa, you integrate a third-party system by:
-
-1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
-2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
-3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted.
-
-***
-
-## Next Chapters: Sync Brands Example
-
-In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will:
-
-1. Integrate a dummy third-party CMS in the Brand Module.
-2. Sync brands to the CMS when a brand is created.
-3. Sync brands from the CMS at a daily schedule.
-
-
# Customizations Next Steps: Learn the Fundamentals
The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS.
@@ -1534,6 +1488,29 @@ Medusa provides the tooling to create a plugin package, test it in a local Medus
To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+# Integrate Third-Party Systems
+
+Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.
+
+Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.
+
+In Medusa, you integrate a third-party system by:
+
+1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
+2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
+3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted.
+
+***
+
+## Next Chapters: Sync Brands Example
+
+In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will:
+
+1. Integrate a dummy third-party CMS in the Brand Module.
+2. Sync brands to the CMS when a brand is created.
+3. Sync brands from the CMS at a daily schedule.
+
+
# General Medusa Application Deployment Guide
In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform.
@@ -2188,6 +2165,29 @@ Medusa's Testing Framework works for integration tests only. You can write unit
The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests.
+# Extend Core Commerce Features
+
+In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
+
+In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
+
+Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
+
+- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
+- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
+- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
+
+***
+
+## Next Chapters: Link Brands to Products Example
+
+The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
+
+- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
+- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
+- Retrieve a product's associated brand's details.
+
+
# Admin Development
In the next chapters, you'll learn more about possible admin customizations.
@@ -2214,65 +2214,6 @@ Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.m
To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
-# API Routes
-
-In this chapter, you’ll learn what API Routes are and how to create them.
-
-## What is an API Route?
-
-An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
-
-The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
-
-***
-
-## How to Create an API Route?
-
-An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
-
-
-
-Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
-
-For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content:
-
-```ts title="src/api/hello-world/route.ts"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[GET] Hello world!",
- })
-}
-```
-
-### Test API Route
-
-To test the API route above, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, send a `GET` request to the `/hello-world` API Route:
-
-```bash
-curl http://localhost:9000/hello-world
-```
-
-***
-
-## When to Use API Routes
-
-You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
-
-
# Custom CLI Scripts
In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
@@ -2447,154 +2388,63 @@ For example, the Blog Module's service would have methods like `retrievePost` an
Refer to the [Service Factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) chapter to learn more about how to extend the service factory and manage data models, and refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for the full list of generated methods and how to use them.
-# Medusa Container
+# API Routes
-In this chapter, you’ll learn about the Medusa container and how to use it.
+In this chapter, you’ll learn what API Routes are and how to create them.
-## What is the Medusa Container?
+## What is an API Route?
-The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
+An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
-In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
+The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
-Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
+***
-For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
+## How to Create an API Route?
-```ts highlights={highlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
-export async function GET(
+
+
+Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
+
+For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content:
+
+```ts title="src/api/hello-world/route.ts"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = (
req: MedusaRequest,
res: MedusaResponse
-) {
- const query = req.scope.resolve("query")
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
+) => {
res.json({
- products,
+ message: "[GET] Hello world!",
})
}
```
-The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
+### Test API Route
-You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+To test the API route above, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, send a `GET` request to the `/hello-world` API Route:
+
+```bash
+curl http://localhost:9000/hello-world
+```
***
-## List of Resources Registered in the Medusa Container
+## When to Use API Routes
-Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
-
-***
-
-## How to Resolve From the Medusa Container
-
-This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
-
-### Subscriber
-
-A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={subscriberHighlights}
-import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function productCreateHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: data.id,
- },
- })
-
- console.log(`You created a product with the title ${products[0].title}`)
-}
-
-export const config: SubscriberConfig = {
- event: `product.created`,
-}
-```
-
-### Scheduled Job
-
-A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={scheduledJobHighlights}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- console.log(
- `You have ${products.length} matching your filters.`
- )
-}
-
-export const config = {
- name: "every-minute-message",
- // execute every minute
- schedule: "* * * * *",
-}
-```
-
-### Workflow Step
-
-A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={workflowStepsHighlight}
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- return new StepResponse(products)
-})
-```
-
-### Module Services and Loaders
-
-A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
-
-Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
+You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
# Environment Variables
@@ -2778,6 +2628,156 @@ Medusa provides two Event Modules out of the box:
Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md).
+# Medusa Container
+
+In this chapter, you’ll learn about the Medusa container and how to use it.
+
+## What is the Medusa Container?
+
+The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
+
+In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
+
+Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
+
+For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
+
+```ts highlights={highlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const query = req.scope.resolve("query")
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ res.json({
+ products,
+ })
+}
+```
+
+The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
+
+You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+***
+
+## List of Resources Registered in the Medusa Container
+
+Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
+
+***
+
+## How to Resolve From the Medusa Container
+
+This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
+
+### Subscriber
+
+A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={subscriberHighlights}
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function productCreateHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: data.id,
+ },
+ })
+
+ console.log(`You created a product with the title ${products[0].title}`)
+}
+
+export const config: SubscriberConfig = {
+ event: `product.created`,
+}
+```
+
+### Scheduled Job
+
+A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={scheduledJobHighlights}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ console.log(
+ `You have ${products.length} matching your filters.`
+ )
+}
+
+export const config = {
+ name: "every-minute-message",
+ // execute every minute
+ schedule: "* * * * *",
+}
+```
+
+### Workflow Step
+
+A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={workflowStepsHighlight}
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ return new StepResponse(products)
+})
+```
+
+### Module Services and Loaders
+
+A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
+
+Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
+
+
# Define Module Link
In this chapter, you’ll learn what a module link is and how to define one.
@@ -3425,171 +3425,6 @@ This will create a post and return it in the response:
You can also execute the workflow from a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) when an event occurs, or from a [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) to run it at a specified interval.
-# Worker Mode of Medusa Instance
-
-In this chapter, you'll learn about the different modes of running a Medusa instance and how to configure the mode.
-
-## What is Worker Mode?
-
-By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
-
-In a production environment, you should deploy two separate instances of your Medusa application:
-
-1. A server instance that handles incoming requests to the application's API routes.
-2. A worker instance that processes background tasks. This includes scheduled jobs and subscribers.
-
-You don't need to set up different projects for each instance. Instead, you can configure the Medusa application to run in different modes based on environment variables, as you'll see later in this chapter.
-
-This separation ensures that the server instance remains responsive to incoming requests, while the worker instance processes tasks in the background.
-
-
-
-***
-
-## How to Set Worker Mode
-
-You can set the worker mode of your application using the `projectConfig.workerMode` configuration in the `medusa-config.ts`. The `workerMode` configuration accepts the following values:
-
-- `shared`: (default) run the application in a single process, meaning the worker and server run in the same process.
-- `worker`: run a worker process only.
-- `server`: run the application server only.
-
-Instead of creating different projects with different worker mode configurations, you can set the worker mode using an environment variable. Then, the worker mode configuration will change based on the environment variable.
-
-For example, set the worker mode in `medusa-config.ts` to the following:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- workerMode: process.env.WORKER_MODE || "shared",
- // ...
- },
- // ...
-})
-```
-
-You set the worker mode configuration to the `process.env.WORKER_MODE` environment variable and set a default value of `shared`.
-
-Then, in the deployed server Medusa instance, set `WORKER_MODE` to `server`, and in the worker Medusa instance, set `WORKER_MODE` to `worker`:
-
-### Server Medusa Instance
-
-```bash
-WORKER_MODE=server
-```
-
-### Worker Medusa Instance
-
-```bash
-WORKER_MODE=worker
-```
-
-### Disable Admin in Worker Mode
-
-Since the worker instance only processes background tasks, you should disable the admin interface in it. That will save resources in the worker instance.
-
-To disable the admin interface, set the `admin.disable` configuration in the `medusa-config.ts` file:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- admin: {
- disable: process.env.ADMIN_DISABLED === "true" ||
- false,
- },
- // ...
-})
-```
-
-Similar to before, you set the value in an environment variable, allowing you to enable or disable the admin interface based on the environment.
-
-Then, in the deployed server Medusa instance, set `ADMIN_DISABLED` to `false`, and in the worker Medusa instance, set `ADMIN_DISABLED` to `true`:
-
-### Server Medusa Instance
-
-```bash
-ADMIN_DISABLED=false
-```
-
-### Worker Medusa Instance
-
-```bash
-ADMIN_DISABLED=true
-```
-
-
-# Medusa's Architecture
-
-In this chapter, you'll learn about the architectural layers in Medusa.
-
-Find the full architectural diagram at the [end of this chapter](#full-diagram-of-medusas-architecture).
-
-## HTTP, Workflow, and Module Layers
-
-Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes.
-
-In a common Medusa application, requests go through four layers in the stack. In order of entry, those are:
-
-1. API Routes (HTTP): Our API Routes are the typical entry point. The Medusa server is based on Express.js, which handles incoming requests. It can also connect to a Redis database that stores the server session data.
-2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application.
-3. Modules: Workflows use domain-specific modules for resource management.
-4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases.
-
-These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-
-
-***
-
-## Database Layer
-
-The Medusa application injects into each module, including your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database.
-
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-
-
-***
-
-## Third-Party Integrations Layer
-
-Third-party services and systems are integrated through Medusa's Commerce and Architectural modules. You also create custom third-party integrations through a [custom module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
-
-### Commerce Modules
-
-[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you can integrate [Stripe](https://docs.medusajs.com/resources/commerce-modules/payment/payment-provider/stripe/index.html.md) through a Payment Module Provider, or [ShipStation](https://docs.medusajs.com/resources/integrations/guides/shipstation/index.html.md) through a Fulfillment Module Provider.
-
-You can also integrate third-party services for custom functionalities. For example, you can integrate [Sanity](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md) for rich CMS capabilities, or [Odoo](https://docs.medusajs.com/resources/recipes/erp/odoo/index.html.md) to sync your Medusa application with your ERP system.
-
-You can replace any of the third-party services mentioned above to build your preferred commerce ecosystem.
-
-
-
-### Architectural Modules
-
-[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. Medusa has the following Architectural modules:
-
-- [Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md).
-- [Event Module](https://docs.medusajs.com/resources/architectural-modules/event/index.html.md): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) as the pub/sub system.
-- [File Module](https://docs.medusajs.com/resources/architectural-modules/file/index.html.md): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) for file storage.
-- [Locking Module](https://docs.medusajs.com/resources/architectural-modules/locking/index.html.md): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/locking/redis/index.html.md) for locking.
-- [Notification Module](https://docs.medusajs.com/resources/architectural-modules/notification/index.html.md): Sends notifications to customers and users, such as for order updates or newsletters. You can integrate [SendGrid](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) for sending emails.
-- [Workflow Engine Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/index.html.md): Orchestrates workflows that hold the business logic of your application. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) to orchestrate workflows.
-
-All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem.
-
-
-
-***
-
-## Full Diagram of Medusa's Architecture
-
-The following diagram illustrates Medusa's architecture including all its layers.
-
-
-
-
# Workflows
In this chapter, you’ll learn about workflows and how to define and execute them.
@@ -3844,6 +3679,237 @@ You can now execute this workflow in a custom API route, scheduled job, or subsc
Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows.
+# Medusa's Architecture
+
+In this chapter, you'll learn about the architectural layers in Medusa.
+
+Find the full architectural diagram at the [end of this chapter](#full-diagram-of-medusas-architecture).
+
+## HTTP, Workflow, and Module Layers
+
+Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes.
+
+In a common Medusa application, requests go through four layers in the stack. In order of entry, those are:
+
+1. API Routes (HTTP): Our API Routes are the typical entry point. The Medusa server is based on Express.js, which handles incoming requests. It can also connect to a Redis database that stores the server session data.
+2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application.
+3. Modules: Workflows use domain-specific modules for resource management.
+4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases.
+
+These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+
+
+***
+
+## Database Layer
+
+The Medusa application injects into each module, including your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database.
+
+Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+
+
+***
+
+## Third-Party Integrations Layer
+
+Third-party services and systems are integrated through Medusa's Commerce and Architectural modules. You also create custom third-party integrations through a [custom module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+
+Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+### Commerce Modules
+
+[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you can integrate [Stripe](https://docs.medusajs.com/resources/commerce-modules/payment/payment-provider/stripe/index.html.md) through a Payment Module Provider, or [ShipStation](https://docs.medusajs.com/resources/integrations/guides/shipstation/index.html.md) through a Fulfillment Module Provider.
+
+You can also integrate third-party services for custom functionalities. For example, you can integrate [Sanity](https://docs.medusajs.com/resources/integrations/guides/sanity/index.html.md) for rich CMS capabilities, or [Odoo](https://docs.medusajs.com/resources/recipes/erp/odoo/index.html.md) to sync your Medusa application with your ERP system.
+
+You can replace any of the third-party services mentioned above to build your preferred commerce ecosystem.
+
+
+
+### Architectural Modules
+
+[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. Medusa has the following Architectural modules:
+
+- [Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/index.html.md): Caches data that require heavy computation. You can integrate a custom module to handle the caching with services like Memcached, or use the existing [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md).
+- [Event Module](https://docs.medusajs.com/resources/architectural-modules/event/index.html.md): A pub/sub system that allows you to subscribe to events and trigger them. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) as the pub/sub system.
+- [File Module](https://docs.medusajs.com/resources/architectural-modules/file/index.html.md): Manages file uploads and storage, such as upload of product images. You can integrate [AWS S3](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) for file storage.
+- [Locking Module](https://docs.medusajs.com/resources/architectural-modules/locking/index.html.md): Manages access to shared resources by multiple processes or threads, preventing conflict between processes and ensuring data consistency. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/locking/redis/index.html.md) for locking.
+- [Notification Module](https://docs.medusajs.com/resources/architectural-modules/notification/index.html.md): Sends notifications to customers and users, such as for order updates or newsletters. You can integrate [SendGrid](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) for sending emails.
+- [Workflow Engine Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/index.html.md): Orchestrates workflows that hold the business logic of your application. You can integrate [Redis](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) to orchestrate workflows.
+
+All of the third-party services mentioned above can be replaced to help you build your preferred architecture and ecosystem.
+
+
+
+***
+
+## Full Diagram of Medusa's Architecture
+
+The following diagram illustrates Medusa's architecture including all its layers.
+
+
+
+
+# Worker Mode of Medusa Instance
+
+In this chapter, you'll learn about the different modes of running a Medusa instance and how to configure the mode.
+
+## What is Worker Mode?
+
+By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
+
+In a production environment, you should deploy two separate instances of your Medusa application:
+
+1. A server instance that handles incoming requests to the application's API routes.
+2. A worker instance that processes background tasks. This includes scheduled jobs and subscribers.
+
+You don't need to set up different projects for each instance. Instead, you can configure the Medusa application to run in different modes based on environment variables, as you'll see later in this chapter.
+
+This separation ensures that the server instance remains responsive to incoming requests, while the worker instance processes tasks in the background.
+
+
+
+***
+
+## How to Set Worker Mode
+
+You can set the worker mode of your application using the `projectConfig.workerMode` configuration in the `medusa-config.ts`. The `workerMode` configuration accepts the following values:
+
+- `shared`: (default) run the application in a single process, meaning the worker and server run in the same process.
+- `worker`: run a worker process only.
+- `server`: run the application server only.
+
+Instead of creating different projects with different worker mode configurations, you can set the worker mode using an environment variable. Then, the worker mode configuration will change based on the environment variable.
+
+For example, set the worker mode in `medusa-config.ts` to the following:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ workerMode: process.env.WORKER_MODE || "shared",
+ // ...
+ },
+ // ...
+})
+```
+
+You set the worker mode configuration to the `process.env.WORKER_MODE` environment variable and set a default value of `shared`.
+
+Then, in the deployed server Medusa instance, set `WORKER_MODE` to `server`, and in the worker Medusa instance, set `WORKER_MODE` to `worker`:
+
+### Server Medusa Instance
+
+```bash
+WORKER_MODE=server
+```
+
+### Worker Medusa Instance
+
+```bash
+WORKER_MODE=worker
+```
+
+### Disable Admin in Worker Mode
+
+Since the worker instance only processes background tasks, you should disable the admin interface in it. That will save resources in the worker instance.
+
+To disable the admin interface, set the `admin.disable` configuration in the `medusa-config.ts` file:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ admin: {
+ disable: process.env.ADMIN_DISABLED === "true" ||
+ false,
+ },
+ // ...
+})
+```
+
+Similar to before, you set the value in an environment variable, allowing you to enable or disable the admin interface based on the environment.
+
+Then, in the deployed server Medusa instance, set `ADMIN_DISABLED` to `false`, and in the worker Medusa instance, set `ADMIN_DISABLED` to `true`:
+
+### Server Medusa Instance
+
+```bash
+ADMIN_DISABLED=false
+```
+
+### Worker Medusa Instance
+
+```bash
+ADMIN_DISABLED=true
+```
+
+
+# Next.js Starter Storefront
+
+The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
+
+The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
+
+In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md).
+
+## Install Next.js Starter
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+
+If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps:
+
+1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
+
+```bash
+git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
+```
+
+2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
+
+```bash npm2yarn
+cd my-medusa-storefront
+npm install
+mv .env.template .env.local
+```
+
+3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys
+
+```bash
+NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123...
+```
+
+4. While the Medusa application is running, start the Next.js Starter storefront:
+
+```bash npm2yarn
+npm run dev
+```
+
+Your Next.js Starter storefront is now running at `http://localhost:8000`.
+
+***
+
+## Customize Storefront
+
+To customize the storefront, refer to the following directories:
+
+- `src/app`: The storefront’s pages.
+- `src/modules`: The storefront’s components.
+- `src/styles`: The storefront’s styles.
+
+You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
+
+***
+
+## Configurations and Integrations
+
+The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary.
+
+Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
+
+
# Usage Information
At Medusa, we strive to provide the best experience for developers using our platform. For that reason, Medusa collects anonymous and non-sensitive data that provides a global understanding of how users are using Medusa.
@@ -3934,70 +4000,350 @@ MEDUSA_FF_ANALYTICS=false
```
-# Next.js Starter Storefront
+# Guide: Create Brand API Route
-The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
+In the previous two chapters, you created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that added the concepts of brands to your application, then created a [workflow to create a brand](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md). In this chapter, you'll expose an API route that allows admin users to create a brand using the workflow from the previous chapter.
-The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
+An API Route is an endpoint that acts as an entry point for other clients to interact with your Medusa customizations, such as the admin dashboard, storefronts, or third-party systems.
-In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md).
-
-## Install Next.js Starter
+The Medusa core application provides a set of [admin](https://docs.medusajs.com/api/admin) and [store](https://docs.medusajs.com/api/store) API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
### Prerequisites
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
+- [createBrandWorkflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md)
-If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps:
+## 1. Create the API Route
-1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
+You create an API route in a `route.{ts,js}` file under a sub-directory of the `src/api` directory. The file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
-```bash
-git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
+Learn more about API routes [in this guide](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md).
+
+The route's path is the path of `route.{ts,js}` relative to `src/api`. So, to create the API route at `/admin/brands`, create the file `src/api/admin/brands/route.ts` with the following content:
+
+
+
+```ts title="src/api/admin/brands/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ createBrandWorkflow,
+} from "../../../workflows/create-brand"
+
+type PostAdminCreateBrandType = {
+ name: string
+}
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const { result } = await createBrandWorkflow(req.scope)
+ .run({
+ input: req.validatedBody,
+ })
+
+ res.json({ brand: result })
+}
```
-2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
+You export a route handler function with its name (`POST`) being the HTTP method of the API route you're exposing.
-```bash npm2yarn
-cd my-medusa-storefront
-npm install
-mv .env.template .env.local
+The function receives two parameters: a `MedusaRequest` object to access request details, and `MedusaResponse` object to return or manipulate the response. The `MedusaRequest` object's `scope` property is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that holds framework tools and custom and core modules' services.
+
+`MedusaRequest` accepts the request body's type as a type argument.
+
+In the API route's handler, you execute the `createBrandWorkflow` by invoking it and passing the Medusa container `req.scope` as a parameter, then invoking its `run` method. You pass the workflow's input in the `input` property of the `run` method's parameter. You pass the request body's parameters using the `validatedBody` property of `MedusaRequest`.
+
+You return a JSON response with the created brand using the `res.json` method.
+
+***
+
+## 2. Create Validation Schema
+
+The API route you created accepts the brand's name in the request body. So, you'll create a schema used to validate incoming request body parameters.
+
+Medusa uses [Zod](https://zod.dev/) to create validation schemas. These schemas are then used to validate incoming request bodies or query parameters.
+
+Learn more about API route validation in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
+
+You create a validation schema in a TypeScript or JavaScript file under a sub-directory of the `src/api` directory. So, create the file `src/api/admin/brands/validators.ts` with the following content:
+
+
+
+```ts title="src/api/admin/brands/validators.ts"
+import { z } from "zod"
+
+export const PostAdminCreateBrand = z.object({
+ name: z.string(),
+})
```
-3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys
+You export a validation schema that expects in the request body an object having a `name` property whose value is a string.
-```bash
-NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123...
+You can then replace `PostAdminCreateBrandType` in `src/api/admin/brands/route.ts` with the following:
+
+```ts title="src/api/admin/brands/route.ts"
+// ...
+import { z } from "zod"
+import { PostAdminCreateBrand } from "./validators"
+
+type PostAdminCreateBrandType = z.infer
+
+// ...
```
-4. While the Medusa application is running, start the Next.js Starter storefront:
+***
+
+## 3. Add Validation Middleware
+
+A middleware is a function executed before the route handler when a request is sent to an API Route. It's useful to guard API routes, parse custom request body types, and apply validation on an API route.
+
+Learn more about middlewares in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md).
+
+Medusa provides a `validateAndTransformBody` middleware that accepts a Zod validation schema and returns a response error if a request is sent with body parameters that don't satisfy the validation schema.
+
+Middlewares are defined in the special file `src/api/middlewares.ts`. So, to add the validation middleware on the API route you created in the previous step, create the file `src/api/middlewares.ts` with the following content:
+
+
+
+```ts title="src/api/middlewares.ts"
+import {
+ defineMiddlewares,
+ validateAndTransformBody,
+} from "@medusajs/framework/http"
+import { PostAdminCreateBrand } from "./admin/brands/validators"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/admin/brands",
+ method: "POST",
+ middlewares: [
+ validateAndTransformBody(PostAdminCreateBrand),
+ ],
+ },
+ ],
+})
+```
+
+You define the middlewares using the `defineMiddlewares` function and export its returned value. The function accepts an object having a `routes` property, which is an array of middleware objects.
+
+In the middleware object, you define three properties:
+
+- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. You pass the create brand's route `/admin/brand`.
+- `method`: The HTTP method to restrict the middleware to, which is `POST`.
+- `middlewares`: An array of middlewares to apply on the route. You pass the `validateAndTransformBody` middleware, passing it the Zod schema you created earlier.
+
+The Medusa application will now validate the body parameters of `POST` requests sent to `/admin/brands` to ensure they match the Zod validation schema. If not, an error is returned in the response specifying the issues to fix in the request body.
+
+***
+
+## Test API Route
+
+To test out the API route, start the Medusa application with the following command:
```bash npm2yarn
npm run dev
```
-Your Next.js Starter storefront is now running at `http://localhost:8000`.
+Since the `/admin/brands` API route has a `/admin` prefix, it's only accessible by authenticated admin users.
+
+So, to retrieve an authenticated token of your admin user, send a `POST` request to the `/auth/user/emailpass` API Route:
+
+```bash
+curl -X POST 'http://localhost:9000/auth/user/emailpass' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "admin@medusa-test.com",
+ "password": "supersecret"
+}'
+```
+
+Make sure to replace the email and password with your admin user's credentials.
+
+Don't have an admin user? Refer to [this guide](https://docs.medusajs.com/learn/installation#create-medusa-admin-user/index.html.md).
+
+Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header:
+
+```bash
+curl -X POST 'http://localhost:9000/admin/brands' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "name": "Acme"
+}'
+```
+
+This returns the created brand in the response:
+
+```json title="Example Response"
+{
+ "brand": {
+ "id": "01J7AX9ES4X113HKY6C681KDZJ",
+ "name": "Acme",
+ "created_at": "2024-09-09T08:09:34.244Z",
+ "updated_at": "2024-09-09T08:09:34.244Z"
+ }
+}
+```
***
-## Customize Storefront
+## Summary
-To customize the storefront, refer to the following directories:
+By following the previous example chapters, you implemented a custom feature that allows admin users to create a brand. You did that by:
-- `src/app`: The storefront’s pages.
-- `src/modules`: The storefront’s components.
-- `src/styles`: The storefront’s styles.
-
-You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
+1. Creating a module that defines and manages a `brand` table in the database.
+2. Creating a workflow that uses the module's service to create a brand record, and implements the compensation logic to delete that brand in case an error occurs.
+3. Creating an API route that allows admin users to create a brand.
***
-## Configurations and Integrations
+## Next Steps: Associate Brand with Product
-The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary.
+Now that you have brands in your Medusa application, you want to associate a brand with a product, which is defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
-Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
+In the next chapters, you'll learn how to build associations between data models defined in different modules.
+
+
+# Guide: Create Brand Workflow
+
+This chapter builds on the work from the [previous chapter](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) where you created a Brand Module.
+
+After adding custom modules to your application, you build commerce features around them using workflows. A workflow is a series of queries and actions, called steps, that complete a task spanning across modules. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features.
+
+The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation.
+
+Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+
+***
+
+## 1. Create createBrandStep
+
+A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using `createStep` from the Workflows SDK
+
+The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand.ts` with the following content:
+
+
+
+```ts title="src/workflows/create-brand.ts"
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { BRAND_MODULE } from "../modules/brand"
+import BrandModuleService from "../modules/brand/service"
+
+export type CreateBrandStepInput = {
+ name: string
+}
+
+export const createBrandStep = createStep(
+ "create-brand-step",
+ async (input: CreateBrandStepInput, { container }) => {
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+
+ const brand = await brandModuleService.createBrands(input)
+
+ return new StepResponse(brand, brand.id)
+ }
+)
+```
+
+You create a `createBrandStep` using the `createStep` function. It accepts the step's unique name as a first parameter, and the step's function as a second parameter.
+
+The step function receives two parameters: input passed to the step when it's invoked, and an object of general context and configurations. This object has a `container` property, which is the Medusa container.
+
+The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) is a registry of framework and commerce tools accessible in your customizations, such as a workflow's step. The Medusa application registers the services of core and custom modules in the container, allowing you to resolve and use them.
+
+So, In the step function, you use the Medusa container to resolve the Brand Module's service and use its generated `createBrands` method, which accepts an object of brands to create.
+
+Learn more about the generated `create` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/create/index.html.md).
+
+A step must return an instance of `StepResponse`. Its first parameter is the data returned by the step, and the second is the data passed to the compensation function, which you'll learn about next.
+
+### Add Compensation Function to Step
+
+You define for each step a compensation function that's executed when an error occurs in the workflow. The compensation function defines the logic to roll-back the changes made by the step. This ensures your data remains consistent if an error occurs, which is especially useful when you integrate third-party services.
+
+Learn more about the compensation function in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
+
+To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
+
+```ts title="src/workflows/create-brand.ts"
+export const createBrandStep = createStep(
+ // ...
+ async (id: string, { container }) => {
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+
+ await brandModuleService.deleteBrands(id)
+ }
+)
+```
+
+The compensation function's first parameter is the brand's ID which you passed as a second parameter to the step function's returned `StepResponse`. It also accepts a context object with a `container` property as a second parameter, similar to the step function.
+
+In the compensation function, you resolve the Brand Module's service from the Medusa container, then use its generated `deleteBrands` method to delete the brand created by the step. This method accepts the ID of the brand to delete.
+
+Learn more about the generated `delete` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/delete/index.html.md).
+
+So, if an error occurs during the workflow's execution, the brand that was created by the step is deleted to maintain data consistency.
+
+***
+
+## 2. Create createBrandWorkflow
+
+You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use `createWorkflow` from the Workflows SDK to create the workflow.
+
+Add the following content in the same `src/workflows/create-brand.ts` file:
+
+```ts title="src/workflows/create-brand.ts"
+// other imports...
+import {
+ // ...
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+// ...
+
+type CreateBrandWorkflowInput = {
+ name: string
+}
+
+export const createBrandWorkflow = createWorkflow(
+ "create-brand",
+ (input: CreateBrandWorkflowInput) => {
+ const brand = createBrandStep(input)
+
+ return new WorkflowResponse(brand)
+ }
+)
+```
+
+You create the `createBrandWorkflow` using the `createWorkflow` function. This function accepts two parameters: the workflow's unique name, and the workflow's constructor function holding the workflow's implementation.
+
+The constructor function accepts the workflow's input as a parameter. In the function, you invoke the `createBrandStep` you created in the previous step to create a brand.
+
+A workflow must return an instance of `WorkflowResponse`. It accepts as a parameter the data to return to the workflow's executor.
+
+***
+
+## Next Steps: Expose Create Brand API Route
+
+You now have a `createBrandWorkflow` that you can execute to create a brand.
+
+In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter.
# Guide: Implement Brand Module
@@ -4528,352 +4874,6 @@ Your customizations often span across systems, where you need to retrieve data o
In the next chapters, you'll learn about the concepts that facilitate integrating third-party systems in your application. You'll integrate a dummy third-party system and sync the brands between it and the Medusa application.
-# Guide: Create Brand API Route
-
-In the previous two chapters, you created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that added the concepts of brands to your application, then created a [workflow to create a brand](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md). In this chapter, you'll expose an API route that allows admin users to create a brand using the workflow from the previous chapter.
-
-An API Route is an endpoint that acts as an entry point for other clients to interact with your Medusa customizations, such as the admin dashboard, storefronts, or third-party systems.
-
-The Medusa core application provides a set of [admin](https://docs.medusajs.com/api/admin) and [store](https://docs.medusajs.com/api/store) API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
-
-### Prerequisites
-
-- [createBrandWorkflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md)
-
-## 1. Create the API Route
-
-You create an API route in a `route.{ts,js}` file under a sub-directory of the `src/api` directory. The file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
-
-Learn more about API routes [in this guide](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md).
-
-The route's path is the path of `route.{ts,js}` relative to `src/api`. So, to create the API route at `/admin/brands`, create the file `src/api/admin/brands/route.ts` with the following content:
-
-
-
-```ts title="src/api/admin/brands/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- createBrandWorkflow,
-} from "../../../workflows/create-brand"
-
-type PostAdminCreateBrandType = {
- name: string
-}
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const { result } = await createBrandWorkflow(req.scope)
- .run({
- input: req.validatedBody,
- })
-
- res.json({ brand: result })
-}
-```
-
-You export a route handler function with its name (`POST`) being the HTTP method of the API route you're exposing.
-
-The function receives two parameters: a `MedusaRequest` object to access request details, and `MedusaResponse` object to return or manipulate the response. The `MedusaRequest` object's `scope` property is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that holds framework tools and custom and core modules' services.
-
-`MedusaRequest` accepts the request body's type as a type argument.
-
-In the API route's handler, you execute the `createBrandWorkflow` by invoking it and passing the Medusa container `req.scope` as a parameter, then invoking its `run` method. You pass the workflow's input in the `input` property of the `run` method's parameter. You pass the request body's parameters using the `validatedBody` property of `MedusaRequest`.
-
-You return a JSON response with the created brand using the `res.json` method.
-
-***
-
-## 2. Create Validation Schema
-
-The API route you created accepts the brand's name in the request body. So, you'll create a schema used to validate incoming request body parameters.
-
-Medusa uses [Zod](https://zod.dev/) to create validation schemas. These schemas are then used to validate incoming request bodies or query parameters.
-
-Learn more about API route validation in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
-
-You create a validation schema in a TypeScript or JavaScript file under a sub-directory of the `src/api` directory. So, create the file `src/api/admin/brands/validators.ts` with the following content:
-
-
-
-```ts title="src/api/admin/brands/validators.ts"
-import { z } from "zod"
-
-export const PostAdminCreateBrand = z.object({
- name: z.string(),
-})
-```
-
-You export a validation schema that expects in the request body an object having a `name` property whose value is a string.
-
-You can then replace `PostAdminCreateBrandType` in `src/api/admin/brands/route.ts` with the following:
-
-```ts title="src/api/admin/brands/route.ts"
-// ...
-import { z } from "zod"
-import { PostAdminCreateBrand } from "./validators"
-
-type PostAdminCreateBrandType = z.infer
-
-// ...
-```
-
-***
-
-## 3. Add Validation Middleware
-
-A middleware is a function executed before the route handler when a request is sent to an API Route. It's useful to guard API routes, parse custom request body types, and apply validation on an API route.
-
-Learn more about middlewares in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md).
-
-Medusa provides a `validateAndTransformBody` middleware that accepts a Zod validation schema and returns a response error if a request is sent with body parameters that don't satisfy the validation schema.
-
-Middlewares are defined in the special file `src/api/middlewares.ts`. So, to add the validation middleware on the API route you created in the previous step, create the file `src/api/middlewares.ts` with the following content:
-
-
-
-```ts title="src/api/middlewares.ts"
-import {
- defineMiddlewares,
- validateAndTransformBody,
-} from "@medusajs/framework/http"
-import { PostAdminCreateBrand } from "./admin/brands/validators"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/admin/brands",
- method: "POST",
- middlewares: [
- validateAndTransformBody(PostAdminCreateBrand),
- ],
- },
- ],
-})
-```
-
-You define the middlewares using the `defineMiddlewares` function and export its returned value. The function accepts an object having a `routes` property, which is an array of middleware objects.
-
-In the middleware object, you define three properties:
-
-- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. You pass the create brand's route `/admin/brand`.
-- `method`: The HTTP method to restrict the middleware to, which is `POST`.
-- `middlewares`: An array of middlewares to apply on the route. You pass the `validateAndTransformBody` middleware, passing it the Zod schema you created earlier.
-
-The Medusa application will now validate the body parameters of `POST` requests sent to `/admin/brands` to ensure they match the Zod validation schema. If not, an error is returned in the response specifying the issues to fix in the request body.
-
-***
-
-## Test API Route
-
-To test out the API route, start the Medusa application with the following command:
-
-```bash npm2yarn
-npm run dev
-```
-
-Since the `/admin/brands` API route has a `/admin` prefix, it's only accessible by authenticated admin users.
-
-So, to retrieve an authenticated token of your admin user, send a `POST` request to the `/auth/user/emailpass` API Route:
-
-```bash
-curl -X POST 'http://localhost:9000/auth/user/emailpass' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "admin@medusa-test.com",
- "password": "supersecret"
-}'
-```
-
-Make sure to replace the email and password with your admin user's credentials.
-
-Don't have an admin user? Refer to [this guide](https://docs.medusajs.com/learn/installation#create-medusa-admin-user/index.html.md).
-
-Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header:
-
-```bash
-curl -X POST 'http://localhost:9000/admin/brands' \
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {token}' \
---data '{
- "name": "Acme"
-}'
-```
-
-This returns the created brand in the response:
-
-```json title="Example Response"
-{
- "brand": {
- "id": "01J7AX9ES4X113HKY6C681KDZJ",
- "name": "Acme",
- "created_at": "2024-09-09T08:09:34.244Z",
- "updated_at": "2024-09-09T08:09:34.244Z"
- }
-}
-```
-
-***
-
-## Summary
-
-By following the previous example chapters, you implemented a custom feature that allows admin users to create a brand. You did that by:
-
-1. Creating a module that defines and manages a `brand` table in the database.
-2. Creating a workflow that uses the module's service to create a brand record, and implements the compensation logic to delete that brand in case an error occurs.
-3. Creating an API route that allows admin users to create a brand.
-
-***
-
-## Next Steps: Associate Brand with Product
-
-Now that you have brands in your Medusa application, you want to associate a brand with a product, which is defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
-
-In the next chapters, you'll learn how to build associations between data models defined in different modules.
-
-
-# Guide: Create Brand Workflow
-
-This chapter builds on the work from the [previous chapter](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) where you created a Brand Module.
-
-After adding custom modules to your application, you build commerce features around them using workflows. A workflow is a series of queries and actions, called steps, that complete a task spanning across modules. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features.
-
-The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation.
-
-Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
-
-### Prerequisites
-
-- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-
-***
-
-## 1. Create createBrandStep
-
-A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using `createStep` from the Workflows SDK
-
-The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand.ts` with the following content:
-
-
-
-```ts title="src/workflows/create-brand.ts"
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { BRAND_MODULE } from "../modules/brand"
-import BrandModuleService from "../modules/brand/service"
-
-export type CreateBrandStepInput = {
- name: string
-}
-
-export const createBrandStep = createStep(
- "create-brand-step",
- async (input: CreateBrandStepInput, { container }) => {
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
-
- const brand = await brandModuleService.createBrands(input)
-
- return new StepResponse(brand, brand.id)
- }
-)
-```
-
-You create a `createBrandStep` using the `createStep` function. It accepts the step's unique name as a first parameter, and the step's function as a second parameter.
-
-The step function receives two parameters: input passed to the step when it's invoked, and an object of general context and configurations. This object has a `container` property, which is the Medusa container.
-
-The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) is a registry of framework and commerce tools accessible in your customizations, such as a workflow's step. The Medusa application registers the services of core and custom modules in the container, allowing you to resolve and use them.
-
-So, In the step function, you use the Medusa container to resolve the Brand Module's service and use its generated `createBrands` method, which accepts an object of brands to create.
-
-Learn more about the generated `create` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/create/index.html.md).
-
-A step must return an instance of `StepResponse`. Its first parameter is the data returned by the step, and the second is the data passed to the compensation function, which you'll learn about next.
-
-### Add Compensation Function to Step
-
-You define for each step a compensation function that's executed when an error occurs in the workflow. The compensation function defines the logic to roll-back the changes made by the step. This ensures your data remains consistent if an error occurs, which is especially useful when you integrate third-party services.
-
-Learn more about the compensation function in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
-
-To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
-
-```ts title="src/workflows/create-brand.ts"
-export const createBrandStep = createStep(
- // ...
- async (id: string, { container }) => {
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
-
- await brandModuleService.deleteBrands(id)
- }
-)
-```
-
-The compensation function's first parameter is the brand's ID which you passed as a second parameter to the step function's returned `StepResponse`. It also accepts a context object with a `container` property as a second parameter, similar to the step function.
-
-In the compensation function, you resolve the Brand Module's service from the Medusa container, then use its generated `deleteBrands` method to delete the brand created by the step. This method accepts the ID of the brand to delete.
-
-Learn more about the generated `delete` method's usage in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/delete/index.html.md).
-
-So, if an error occurs during the workflow's execution, the brand that was created by the step is deleted to maintain data consistency.
-
-***
-
-## 2. Create createBrandWorkflow
-
-You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use `createWorkflow` from the Workflows SDK to create the workflow.
-
-Add the following content in the same `src/workflows/create-brand.ts` file:
-
-```ts title="src/workflows/create-brand.ts"
-// other imports...
-import {
- // ...
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-// ...
-
-type CreateBrandWorkflowInput = {
- name: string
-}
-
-export const createBrandWorkflow = createWorkflow(
- "create-brand",
- (input: CreateBrandWorkflowInput) => {
- const brand = createBrandStep(input)
-
- return new WorkflowResponse(brand)
- }
-)
-```
-
-You create the `createBrandWorkflow` using the `createWorkflow` function. This function accepts two parameters: the workflow's unique name, and the workflow's constructor function holding the workflow's implementation.
-
-The constructor function accepts the workflow's input as a parameter. In the function, you invoke the `createBrandStep` you created in the previous step to create a brand.
-
-A workflow must return an instance of `WorkflowResponse`. It accepts as a parameter the data to return to the workflow's executor.
-
-***
-
-## Next Steps: Expose Create Brand API Route
-
-You now have a `createBrandWorkflow` that you can execute to create a brand.
-
-In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter.
-
-
# Guide: Add Product's Brand Widget in Admin
In this chapter, you'll customize the product details page of the Medusa Admin dashboard to show the product's [brand](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md). You'll create a widget that is injected into a pre-defined zone in the page, and in the widget you'll retrieve the product's brand from the server and display it.
@@ -5028,224 +5028,6 @@ The [Admin Components guides](https://docs.medusajs.com/resources/admin-componen
In the next chapter, you'll add a UI route that displays the list of brands in your application and allows admin users.
-# Guide: Define Module Link Between Brand and Product
-
-In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
-
-Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
-
-A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
-
-In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
-
-Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-### Prerequisites
-
-- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-
-## 1. Define Link
-
-Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
-
-So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
-
-
-
-```ts title="src/links/product-brand.ts" highlights={highlights}
-import BrandModule from "../modules/brand"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- isList: true,
- },
- BrandModule.linkable.brand
-)
-```
-
-You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
-
-The `defineLink` function accepts two parameters of the same type, which is either:
-
-- The data model's link configuration, which you access from the Module's `linkable` property;
-- Or an object that has two properties:
- - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
- - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
-
-So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
-
-***
-
-## 2. Sync the Link to the Database
-
-A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
-
-```bash
-npx medusa db:migrate
-```
-
-This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
-
-You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
-
-***
-
-## Next Steps: Extend Create Product Flow
-
-In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
-
-
-# Guide: Query Product's Brands
-
-In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand.
-
-In this chapter, you'll learn how to retrieve a product's brand (and vice-versa) in two ways: Using Medusa's existing API route, or in customizations, such as a custom API route.
-
-### Prerequisites
-
-- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
-
-***
-
-## Approach 1: Retrieve Brands in Existing API Routes
-
-Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
-
-Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
-
-For example, send the following request to retrieve the list of products with their brands:
-
-```bash
-curl 'http://localhost:9000/admin/products?fields=+brand.*' \
---header 'Authorization: Bearer {token}'
-```
-
-Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
-
-Any product that is linked to a brand will have a `brand` property in its object:
-
-```json title="Example Product Object"
-{
- "id": "prod_123",
- // ...
- "brand": {
- "id": "01JEB44M61BRM3ARM2RRMK7GJF",
- "name": "Acme",
- "created_at": "2024-12-05T09:59:08.737Z",
- "updated_at": "2024-12-05T09:59:08.737Z",
- "deleted_at": null
- }
-}
-```
-
-By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
-
-### Limitations: Filtering by Brands in Existing API Routes
-
-While you can retrieve linked records using the `fields` query parameter of an existing API route, you can't filter by linked records.
-
-Instead, you'll have to create a custom API route that uses Query to retrieve linked records with filters, as explained in the [Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-filters-and-pagination-on-linked-records/index.html.md).
-
-***
-
-## Approach 2: Use Query to Retrieve Linked Records
-
-You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
-
-Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-
-For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
-
-```ts title="src/api/admin/brands/route.ts" highlights={highlights}
-// other imports...
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve("query")
-
- const { data: brands } = await query.graph({
- entity: "brand",
- fields: ["*", "products.*"],
- })
-
- res.json({ brands })
-}
-```
-
-This adds a `GET` API route at `/admin/brands`. In the API route, you resolve Query from the Medusa container. Query has a `graph` method that runs a query to retrieve data. It accepts an object having the following properties:
-
-- `entity`: The data model's name as specified in the first parameter of `model.define`.
-- `fields`: An array of properties and relations to retrieve. You can pass:
- - A property's name, such as `id`, or `*` for all properties.
- - A relation or linked model's name, such as `products` (use the plural name since brands are linked to list of products). You suffix the name with `.*` to retrieve all its properties.
-
-`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
-
-### Test it Out
-
-To test the API route out, send a `GET` request to `/admin/brands`:
-
-```bash
-curl 'http://localhost:9000/admin/brands' \
--H 'Authorization: Bearer {token}'
-```
-
-Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
-
-This returns the brands in your store with their linked products. For example:
-
-```json title="Example Response"
-{
- "brands": [
- {
- "id": "123",
- // ...
- "products": [
- {
- "id": "prod_123",
- // ...
- }
- ]
- }
- ]
-}
-```
-
-### Limitations: Filtering by Brand in Query
-
-While you can use Query to retrieve linked records, you can't filter by linked records.
-
-For an alternative approach, refer to the [Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-filters-and-pagination-on-linked-records/index.html.md).
-
-***
-
-## Summary
-
-By following the examples of the previous chapters, you:
-
-- Defined a link between the Brand and Product modules's data models, allowing you to associate a product with a brand.
-- Extended the create-product workflow and route to allow setting the product's brand while creating the product.
-- Queried a product's brand, and vice versa.
-
-***
-
-## Next Steps: Customize Medusa Admin
-
-Clients, such as the Medusa Admin dashboard, can now use brand-related features, such as creating a brand or setting the brand of a product.
-
-In the next chapters, you'll learn how to customize the Medusa Admin to show a product's brand on its details page, and to show a new page with the list of brands in your store.
-
-
# Guide: Sync Brands from Medusa to Third-Party
In the [previous chapter](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md), you created a CMS Module that integrates a dummy third-party system. You can now perform actions using that module within your custom flows.
@@ -5827,6 +5609,436 @@ By following the previous chapters, you utilized Medusa's framework and orchestr
With Medusa, you can integrate any service from your commerce ecosystem with ease. You don't have to set up separate applications to manage your different customizations, or worry about data inconsistency across systems. Your efforts only go into implementing the business logic that ties your systems together.
+# Guide: Integrate Third-Party Brand System
+
+In the previous chapters, you've created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS.
+
+Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+
+## 1. Create Module Directory
+
+You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.
+
+
+
+***
+
+## 2. Create Module Service
+
+Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
+
+Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
+import { Logger, ConfigModule } from "@medusajs/framework/types"
+
+export type ModuleOptions = {
+ apiKey: string
+}
+
+type InjectedDependencies = {
+ logger: Logger
+ configModule: ConfigModule
+}
+
+class CmsModuleService {
+ private options_: ModuleOptions
+ private logger_: Logger
+
+ constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
+ this.logger_ = logger
+ this.options_ = options
+
+ // TODO initialize SDK
+ }
+}
+
+export default CmsModuleService
+```
+
+You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
+
+1. The module's container. Since a module is [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), it has a [local container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) and resources within the module.
+2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.
+
+When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.
+
+### Integration Methods
+
+Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
+
+Add the following methods in the `CmsModuleService`:
+
+```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
+export class CmsModuleService {
+ // ...
+
+ // a dummy method to simulate sending a request,
+ // in a realistic scenario, you'd use an SDK, fetch, or axios clients
+ private async sendRequest(url: string, method: string, data?: any) {
+ this.logger_.info(`Sending a ${method} request to ${url}.`)
+ this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
+ this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
+ }
+
+ async createBrand(brand: Record) {
+ await this.sendRequest("/brands", "POST", brand)
+ }
+
+ async deleteBrand(id: string) {
+ await this.sendRequest(`/brands/${id}`, "DELETE")
+ }
+
+ async retrieveBrands(): Promise[]> {
+ await this.sendRequest("/brands", "GET")
+
+ return []
+ }
+}
+```
+
+The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
+
+You also add three methods that use the `sendRequest` method:
+
+- `createBrand` that creates a brand in the third-party system.
+- `deleteBrand` that deletes the brand in the third-party system.
+- `retrieveBrands` to retrieve a brand from the third-party system.
+
+***
+
+## 3. Export Module Definition
+
+After creating the module's service, you'll export the module definition indicating the module's name and service.
+
+Create the file `src/modules/cms/index.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import CmsModuleService from "./service"
+
+export const CMS_MODULE = "cms"
+
+export default Module(CMS_MODULE, {
+ service: CmsModuleService,
+})
+```
+
+You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.
+
+***
+
+## 4. Add Module to Medusa's Configurations
+
+Finally, add the module to the Medusa configurations at `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ // ...
+ {
+ resolve: "./src/modules/cms",
+ options: {
+ apiKey: process.env.CMS_API_KEY,
+ },
+ },
+ ],
+})
+```
+
+The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
+
+You can add the `CMS_API_KEY` environment variable to `.env`:
+
+```bash
+CMS_API_KEY=123
+```
+
+***
+
+## Next Steps: Sync Brand From Medusa to CMS
+
+You can now use the CMS Module's service to perform actions on the third-party CMS.
+
+In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
+
+
+# Guide: Define Module Link Between Brand and Product
+
+In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
+
+Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
+
+A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
+
+In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
+
+Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+### Prerequisites
+
+- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+
+## 1. Define Link
+
+Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
+
+So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
+
+
+
+```ts title="src/links/product-brand.ts" highlights={highlights}
+import BrandModule from "../modules/brand"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ isList: true,
+ },
+ BrandModule.linkable.brand
+)
+```
+
+You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
+
+The `defineLink` function accepts two parameters of the same type, which is either:
+
+- The data model's link configuration, which you access from the Module's `linkable` property;
+- Or an object that has two properties:
+ - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
+ - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
+
+So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
+
+***
+
+## 2. Sync the Link to the Database
+
+A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
+
+```bash
+npx medusa db:migrate
+```
+
+This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
+
+You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
+
+***
+
+## Next Steps: Extend Create Product Flow
+
+In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
+
+
+# Write Tests for Modules
+
+In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## moduleIntegrationTestRunner Utility
+
+`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
+
+For example, assuming you have a `blog` module, create a test file at `src/modules/blog/__tests__/service.spec.ts`:
+
+```ts title="src/modules/blog/__tests__/service.spec.ts"
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import { BLOG_MODULE } from ".."
+import BlogModuleService from "../service"
+import Post from "../models/post"
+
+moduleIntegrationTestRunner({
+ moduleName: BLOG_MODULE,
+ moduleModels: [Post],
+ resolve: "./src/modules/blog",
+ testSuite: ({ service }) => {
+ // TODO write tests
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties:
+
+- `moduleName`: The name of the module.
+- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models.
+- `resolve`: The path to the module's directory.
+- `testSuite`: A function that defines the tests to run.
+
+The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service.
+
+The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property.
+
+The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+
+***
+
+## Run Tests
+
+Run the following command to run your module integration tests:
+
+```bash npm2yarn
+npm run test:integration:modules
+```
+
+If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
+
+***
+
+## Pass Module Options
+
+If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
+
+For example:
+
+```ts
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import BlogModuleService from "../service"
+
+moduleIntegrationTestRunner({
+ moduleOptions: {
+ apiKey: "123",
+ },
+ // ...
+})
+```
+
+***
+
+## Write Tests for Modules without Data Models
+
+If your module doesn't have a data model, pass a dummy model in the `moduleModels` property.
+
+For example:
+
+```ts
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import BlogModuleService from "../service"
+import { model } from "@medusajs/framework/utils"
+
+const DummyModel = model.define("dummy_model", {
+ id: model.id().primaryKey(),
+})
+
+moduleIntegrationTestRunner({
+ moduleModels: [DummyModel],
+ // ...
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+***
+
+### Other Options and Inputs
+
+Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+
+***
+
+## Database Used in Tests
+
+The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+
+To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
+
+
+# Write Integration Tests
+
+In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## medusaIntegrationTestRunner Utility
+
+The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
+
+For example:
+
+```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ api, getContainer }) => {
+ // TODO write tests...
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
+
+`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
+
+- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
+ - `get`: Send a `GET` request to an API route.
+ - `post`: Send a `POST` request to an API route.
+ - `delete`: Send a `DELETE` request to an API route.
+- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
+
+The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+
+### Jest Timeout
+
+Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+
+```ts title="integration-tests/http/test.spec.ts"
+// in your test's file
+jest.setTimeout(60 * 1000)
+```
+
+***
+
+### Run Tests
+
+Run the following command to run your tests:
+
+```bash npm2yarn
+npm run test:integration
+```
+
+If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
+
+***
+
+## Other Options and Inputs
+
+Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+
+***
+
+## Database Used in Tests
+
+The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+
+To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
+
+***
+
+## Example Integration Tests
+
+The next chapters provide examples of writing integration tests for API routes and workflows.
+
+
# Guide: Extend Create Product Flow
After linking the [custom Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) in the [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md), you'll extend the create product workflow and API route to allow associating a brand with a product.
@@ -6039,366 +6251,6 @@ In the Medusa application's logs, you'll find the message `Linked brand to produ
Now that you've extending the create-product flow to link a brand to it, you want to retrieve the brand details of a product. You'll learn how to do so in the next chapter.
-# Guide: Integrate Third-Party Brand System
-
-In the previous chapters, you've created a [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) that adds brands to your application. In this chapter, you'll integrate a dummy Content-Management System (CMS) in a new module. The module's service will provide methods to retrieve and manage brands in the CMS. You'll later use this service to sync data from and to the CMS.
-
-Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-## 1. Create Module Directory
-
-You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.
-
-
-
-***
-
-## 2. Create Module Service
-
-Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
-
-Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
-import { Logger, ConfigModule } from "@medusajs/framework/types"
-
-export type ModuleOptions = {
- apiKey: string
-}
-
-type InjectedDependencies = {
- logger: Logger
- configModule: ConfigModule
-}
-
-class CmsModuleService {
- private options_: ModuleOptions
- private logger_: Logger
-
- constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
- this.logger_ = logger
- this.options_ = options
-
- // TODO initialize SDK
- }
-}
-
-export default CmsModuleService
-```
-
-You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
-
-1. The module's container. Since a module is [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), it has a [local container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) and resources within the module.
-2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.
-
-When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.
-
-### Integration Methods
-
-Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
-
-Add the following methods in the `CmsModuleService`:
-
-```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
-export class CmsModuleService {
- // ...
-
- // a dummy method to simulate sending a request,
- // in a realistic scenario, you'd use an SDK, fetch, or axios clients
- private async sendRequest(url: string, method: string, data?: any) {
- this.logger_.info(`Sending a ${method} request to ${url}.`)
- this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
- this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
- }
-
- async createBrand(brand: Record) {
- await this.sendRequest("/brands", "POST", brand)
- }
-
- async deleteBrand(id: string) {
- await this.sendRequest(`/brands/${id}`, "DELETE")
- }
-
- async retrieveBrands(): Promise[]> {
- await this.sendRequest("/brands", "GET")
-
- return []
- }
-}
-```
-
-The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
-
-You also add three methods that use the `sendRequest` method:
-
-- `createBrand` that creates a brand in the third-party system.
-- `deleteBrand` that deletes the brand in the third-party system.
-- `retrieveBrands` to retrieve a brand from the third-party system.
-
-***
-
-## 3. Export Module Definition
-
-After creating the module's service, you'll export the module definition indicating the module's name and service.
-
-Create the file `src/modules/cms/index.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import CmsModuleService from "./service"
-
-export const CMS_MODULE = "cms"
-
-export default Module(CMS_MODULE, {
- service: CmsModuleService,
-})
-```
-
-You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.
-
-***
-
-## 4. Add Module to Medusa's Configurations
-
-Finally, add the module to the Medusa configurations at `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- // ...
- {
- resolve: "./src/modules/cms",
- options: {
- apiKey: process.env.CMS_API_KEY,
- },
- },
- ],
-})
-```
-
-The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
-
-You can add the `CMS_API_KEY` environment variable to `.env`:
-
-```bash
-CMS_API_KEY=123
-```
-
-***
-
-## Next Steps: Sync Brand From Medusa to CMS
-
-You can now use the CMS Module's service to perform actions on the third-party CMS.
-
-In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
-
-
-# Write Integration Tests
-
-In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
-
-### Prerequisites
-
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-
-## medusaIntegrationTestRunner Utility
-
-The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
-
-For example:
-
-```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-
-medusaIntegrationTestRunner({
- testSuite: ({ api, getContainer }) => {
- // TODO write tests...
- },
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
-
-`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
-
-- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
- - `get`: Send a `GET` request to an API route.
- - `post`: Send a `POST` request to an API route.
- - `delete`: Send a `DELETE` request to an API route.
-- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
-
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
-
-### Jest Timeout
-
-Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
-
-```ts title="integration-tests/http/test.spec.ts"
-// in your test's file
-jest.setTimeout(60 * 1000)
-```
-
-***
-
-### Run Tests
-
-Run the following command to run your tests:
-
-```bash npm2yarn
-npm run test:integration
-```
-
-If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
-
-***
-
-## Other Options and Inputs
-
-Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
-
-***
-
-## Database Used in Tests
-
-The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
-
-To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
-
-***
-
-## Example Integration Tests
-
-The next chapters provide examples of writing integration tests for API routes and workflows.
-
-
-# Write Tests for Modules
-
-In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service.
-
-### Prerequisites
-
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-
-## moduleIntegrationTestRunner Utility
-
-`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
-
-For example, assuming you have a `blog` module, create a test file at `src/modules/blog/__tests__/service.spec.ts`:
-
-```ts title="src/modules/blog/__tests__/service.spec.ts"
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import { BLOG_MODULE } from ".."
-import BlogModuleService from "../service"
-import Post from "../models/post"
-
-moduleIntegrationTestRunner({
- moduleName: BLOG_MODULE,
- moduleModels: [Post],
- resolve: "./src/modules/blog",
- testSuite: ({ service }) => {
- // TODO write tests
- },
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties:
-
-- `moduleName`: The name of the module.
-- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models.
-- `resolve`: The path to the module's directory.
-- `testSuite`: A function that defines the tests to run.
-
-The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service.
-
-The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property.
-
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
-
-***
-
-## Run Tests
-
-Run the following command to run your module integration tests:
-
-```bash npm2yarn
-npm run test:integration:modules
-```
-
-If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
-
-***
-
-## Pass Module Options
-
-If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
-
-For example:
-
-```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import BlogModuleService from "../service"
-
-moduleIntegrationTestRunner({
- moduleOptions: {
- apiKey: "123",
- },
- // ...
-})
-```
-
-***
-
-## Write Tests for Modules without Data Models
-
-If your module doesn't have a data model, pass a dummy model in the `moduleModels` property.
-
-For example:
-
-```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import BlogModuleService from "../service"
-import { model } from "@medusajs/framework/utils"
-
-const DummyModel = model.define("dummy_model", {
- id: model.id().primaryKey(),
-})
-
-moduleIntegrationTestRunner({
- moduleModels: [DummyModel],
- // ...
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-***
-
-### Other Options and Inputs
-
-Refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
-
-***
-
-## Database Used in Tests
-
-The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
-
-To manage that database, such as changing its name or perform operations on it in your tests, refer to [the Test Tooling Reference](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
-
-
# Environment Variables in Admin Customizations
In this chapter, you'll learn how to use environment variables in your admin customizations.
@@ -6476,6 +6328,199 @@ When you build the Medusa application, including the Medusa Admin, with the `bui
For example, the `VITE_MY_API_KEY` environment variable in the example above will be replaced with the actual value during the build process.
+# Admin Development Constraints
+
+This chapter lists some constraints of admin widgets and UI routes.
+
+## Arrow Functions
+
+Widget and UI route components must be created as arrow functions.
+
+```ts highlights={arrowHighlights}
+// Don't
+function ProductWidget() {
+ // ...
+}
+
+// Do
+const ProductWidget = () => {
+ // ...
+}
+```
+
+***
+
+## Widget Zone
+
+A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable.
+
+```ts highlights={zoneHighlights}
+// Don't
+export const config = defineWidgetConfig({
+ zone: `product.details.before`,
+})
+
+// Don't
+const ZONE = "product.details.after"
+export const config = defineWidgetConfig({
+ zone: ZONE,
+})
+
+// Do
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+```
+
+
+# Guide: Query Product's Brands
+
+In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand.
+
+In this chapter, you'll learn how to retrieve a product's brand (and vice-versa) in two ways: Using Medusa's existing API route, or in customizations, such as a custom API route.
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
+
+***
+
+## Approach 1: Retrieve Brands in Existing API Routes
+
+Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
+
+Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
+
+For example, send the following request to retrieve the list of products with their brands:
+
+```bash
+curl 'http://localhost:9000/admin/products?fields=+brand.*' \
+--header 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+Any product that is linked to a brand will have a `brand` property in its object:
+
+```json title="Example Product Object"
+{
+ "id": "prod_123",
+ // ...
+ "brand": {
+ "id": "01JEB44M61BRM3ARM2RRMK7GJF",
+ "name": "Acme",
+ "created_at": "2024-12-05T09:59:08.737Z",
+ "updated_at": "2024-12-05T09:59:08.737Z",
+ "deleted_at": null
+ }
+}
+```
+
+By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
+
+### Limitations: Filtering by Brands in Existing API Routes
+
+While you can retrieve linked records using the `fields` query parameter of an existing API route, you can't filter by linked records.
+
+Instead, you'll have to create a custom API route that uses Query to retrieve linked records with filters, as explained in the [Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-filters-and-pagination-on-linked-records/index.html.md).
+
+***
+
+## Approach 2: Use Query to Retrieve Linked Records
+
+You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
+
+Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
+
+```ts title="src/api/admin/brands/route.ts" highlights={highlights}
+// other imports...
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve("query")
+
+ const { data: brands } = await query.graph({
+ entity: "brand",
+ fields: ["*", "products.*"],
+ })
+
+ res.json({ brands })
+}
+```
+
+This adds a `GET` API route at `/admin/brands`. In the API route, you resolve Query from the Medusa container. Query has a `graph` method that runs a query to retrieve data. It accepts an object having the following properties:
+
+- `entity`: The data model's name as specified in the first parameter of `model.define`.
+- `fields`: An array of properties and relations to retrieve. You can pass:
+ - A property's name, such as `id`, or `*` for all properties.
+ - A relation or linked model's name, such as `products` (use the plural name since brands are linked to list of products). You suffix the name with `.*` to retrieve all its properties.
+
+`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
+
+### Test it Out
+
+To test the API route out, send a `GET` request to `/admin/brands`:
+
+```bash
+curl 'http://localhost:9000/admin/brands' \
+-H 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+This returns the brands in your store with their linked products. For example:
+
+```json title="Example Response"
+{
+ "brands": [
+ {
+ "id": "123",
+ // ...
+ "products": [
+ {
+ "id": "prod_123",
+ // ...
+ }
+ ]
+ }
+ ]
+}
+```
+
+### Limitations: Filtering by Brand in Query
+
+While you can use Query to retrieve linked records, you can't filter by linked records.
+
+For an alternative approach, refer to the [Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query#apply-filters-and-pagination-on-linked-records/index.html.md).
+
+***
+
+## Summary
+
+By following the examples of the previous chapters, you:
+
+- Defined a link between the Brand and Product modules's data models, allowing you to associate a product with a brand.
+- Extended the create-product workflow and route to allow setting the product's brand while creating the product.
+- Queried a product's brand, and vice versa.
+
+***
+
+## Next Steps: Customize Medusa Admin
+
+Clients, such as the Medusa Admin dashboard, can now use brand-related features, such as creating a brand or setting the brand of a product.
+
+In the next chapters, you'll learn how to customize the Medusa Admin to show a product's brand on its details page, and to show a new page with the list of brands in your store.
+
+
# Admin Routing Customizations
The Medusa Admin dashboard uses [React Router](https://reactrouter.com) under the hood to manage routing. So, you can have more flexibility in routing-related customizations using some of React Router's utilities, hooks, and components.
@@ -6760,51 +6805,6 @@ The Medusa Admin dashboard can be displayed in languages other than English, whi
Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/learn/resources/contribution-guidelines/admin-translations/index.html.md).
-# Admin Development Constraints
-
-This chapter lists some constraints of admin widgets and UI routes.
-
-## Arrow Functions
-
-Widget and UI route components must be created as arrow functions.
-
-```ts highlights={arrowHighlights}
-// Don't
-function ProductWidget() {
- // ...
-}
-
-// Do
-const ProductWidget = () => {
- // ...
-}
-```
-
-***
-
-## Widget Zone
-
-A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable.
-
-```ts highlights={zoneHighlights}
-// Don't
-export const config = defineWidgetConfig({
- zone: `product.details.before`,
-})
-
-// Don't
-const ZONE = "product.details.after"
-export const config = defineWidgetConfig({
- zone: ZONE,
-})
-
-// Do
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-```
-
-
# Admin UI Routes
In this chapter, you’ll learn how to create a UI route in the admin dashboard.
@@ -7160,203 +7160,1422 @@ Refer to [this reference](https://docs.medusajs.com/resources/admin-widget-injec
To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components.
-# Pass Additional Data to Medusa's API Route
+# Seed Data with Custom CLI Script
-In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.
+In this chapter, you'll learn how to seed data using a custom CLI script.
-## Why Pass Additional Data?
+## How to Seed Data
-Some of Medusa's API Routes accept an `additional_data` parameter whose type is an object. The API Route passes the `additional_data` to the workflow, which in turn passes it to its hooks.
+To seed dummy data for development or demo purposes, use a custom CLI script.
-This is useful when you have a link from your custom module to a commerce module, and you want to perform an additional action when a request is sent to an existing API route.
+In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
-For example, the [Create Product API Route](https://docs.medusajs.com/api/admin#products_postproducts) accepts an `additional_data` parameter. If you have a data model linked to it, you consume the `productsCreated` hook to create a record of the data model using the custom data and link it to the product.
+### Example: Seed Dummy Products
-### API Routes Accepting Additional Data
+In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-### API Routes List
+First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-- Campaigns
- - [Create Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaigns)
- - [Update Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaignsid)
-- Cart
- - [Create Cart](https://docs.medusajs.com/api/store#carts_postcarts)
- - [Update Cart](https://docs.medusajs.com/api/store#carts_postcartsid)
-- Collections
- - [Create Collection](https://docs.medusajs.com/api/admin#collections_postcollections)
- - [Update Collection](https://docs.medusajs.com/api/admin#collections_postcollectionsid)
-- Customers
- - [Create Customer](https://docs.medusajs.com/api/admin#customers_postcustomers)
- - [Update Customer](https://docs.medusajs.com/api/admin#customers_postcustomersid)
- - [Create Address](https://docs.medusajs.com/api/admin#customers_postcustomersidaddresses)
- - [Update Address](https://docs.medusajs.com/api/admin#customers_postcustomersidaddressesaddress_id)
-- Draft Orders
- - [Create Draft Order](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders)
-- Orders
- - [Complete Orders](https://docs.medusajs.com/api/admin#orders_postordersidcomplete)
- - [Cancel Order's Fulfillment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idcancel)
- - [Create Shipment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idshipments)
- - [Create Fulfillment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillments)
-- Products
- - [Create Product](https://docs.medusajs.com/api/admin#products_postproducts)
- - [Update Product](https://docs.medusajs.com/api/admin#products_postproductsid)
- - [Create Product Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariants)
- - [Update Product Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariantsvariant_id)
- - [Create Product Option](https://docs.medusajs.com/api/admin#products_postproductsidoptions)
- - [Update Product Option](https://docs.medusajs.com/api/admin#products_postproductsidoptionsoption_id)
-- Product Tags
- - [Create Product Tag](https://docs.medusajs.com/api/admin#product-tags_postproducttags)
- - [Update Product Tag](https://docs.medusajs.com/api/admin#product-tags_postproducttagsid)
-- Product Types
- - [Create Product Type](https://docs.medusajs.com/api/admin#product-types_postproducttypes)
- - [Update Product Type](https://docs.medusajs.com/api/admin#product-types_postproducttypesid)
-- Promotions
- - [Create Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotions)
- - [Update Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotionsid)
+```bash npm2yarn
+npm install --save-dev @faker-js/faker
+```
+
+Then, create the file `src/scripts/demo-products.ts` with the following content:
+
+```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+import { ExecArgs } from "@medusajs/framework/types"
+import { faker } from "@faker-js/faker"
+import {
+ ContainerRegistrationKeys,
+ Modules,
+ ProductStatus,
+} from "@medusajs/framework/utils"
+import {
+ createInventoryLevelsWorkflow,
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+
+export default async function seedDummyProducts({
+ container,
+}: ExecArgs) {
+ const salesChannelModuleService = container.resolve(
+ Modules.SALES_CHANNEL
+ )
+ const logger = container.resolve(
+ ContainerRegistrationKeys.LOGGER
+ )
+ const query = container.resolve(
+ ContainerRegistrationKeys.QUERY
+ )
+
+ const defaultSalesChannel = await salesChannelModuleService
+ .listSalesChannels({
+ name: "Default Sales Channel",
+ })
+
+ const sizeOptions = ["S", "M", "L", "XL"]
+ const colorOptions = ["Black", "White"]
+ const currency_code = "eur"
+ const productsNum = 50
+
+ // TODO seed products
+}
+```
+
+So far, in the script, you:
+
+- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
+- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
+- Initialize some default data to use when seeding the products next.
+
+Next, replace the `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+const productsData = new Array(productsNum).fill(0).map((_, index) => {
+ const title = faker.commerce.product() + "_" + index
+ return {
+ title,
+ is_giftcard: true,
+ description: faker.commerce.productDescription(),
+ status: ProductStatus.PUBLISHED,
+ options: [
+ {
+ title: "Size",
+ values: sizeOptions,
+ },
+ {
+ title: "Color",
+ values: colorOptions,
+ },
+ ],
+ images: [
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ ],
+ variants: new Array(10).fill(0).map((_, variantIndex) => ({
+ title: `${title} ${variantIndex}`,
+ sku: `variant-${variantIndex}${index}`,
+ prices: new Array(10).fill(0).map((_, priceIndex) => ({
+ currency_code,
+ amount: 10 * priceIndex,
+ })),
+ options: {
+ Size: sizeOptions[Math.floor(Math.random() * 3)],
+ },
+ })),
+ shipping_profile_id: "sp_123",
+ sales_channels: [
+ {
+ id: defaultSalesChannel[0].id,
+ },
+ ],
+ }
+})
+
+// TODO seed products
+```
+
+You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
+
+Then, replace the new `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+const { result: products } = await createProductsWorkflow(container).run({
+ input: {
+ products: productsData,
+ },
+})
+
+logger.info(`Seeded ${products.length} products.`)
+
+// TODO add inventory levels
+```
+
+You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
+
+Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+logger.info("Seeding inventory levels.")
+
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: ["id"],
+})
+
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
+ fields: ["id"],
+})
+
+const inventoryLevels = inventoryItems.map((inventoryItem) => ({
+ location_id: stockLocations[0].id,
+ stocked_quantity: 1000000,
+ inventory_item_id: inventoryItem.id,
+}))
+
+await createInventoryLevelsWorkflow(container).run({
+ input: {
+ inventory_levels: inventoryLevels,
+ },
+})
+
+logger.info("Finished seeding inventory levels data.")
+```
+
+You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
+
+Then, you generate inventory levels for each inventory item, associating it with the first stock location.
+
+Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
+
+### Test Script
+
+To test out the script, run the following command in your project's directory:
+
+```bash
+npx medusa exec ./src/scripts/demo-products.ts
+```
+
+This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
+
+
+# Data Model Database Index
+
+In this chapter, you’ll learn how to define a database index on a data model.
+
+You can also define an index on a property as explained in the [Properties chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#define-database-index-on-property/index.html.md).
+
+## Define Database Index on Data Model
+
+A data model has an `indexes` method that defines database indices on its properties.
+
+The index can be on multiple columns (composite index). For example:
+
+```ts highlights={dataModelIndexHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ },
+])
+
+export default MyCustom
+```
+
+The `indexes` method receives an array of indices as a parameter. Each index is an object with a required `on` property indicating the properties to apply the index on.
+
+In the above example, you define a composite index on the `name` and `age` properties.
+
+### Index Conditions
+
+An index can have conditions. For example:
+
+```ts highlights={conditionHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ where: {
+ age: 30,
+ },
+ },
+])
+
+export default MyCustom
+```
+
+The index object passed to `indexes` accepts a `where` property whose value is an object of conditions. The object's key is a property's name, and its value is the condition on that property.
+
+In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
+
+A property's condition can be a negation. For example:
+
+```ts highlights={negationHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number().nullable(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ where: {
+ age: {
+ $ne: null,
+ },
+ },
+ },
+])
+
+export default MyCustom
+```
+
+A property's value in `where` can be an object having a `$ne` property. `$ne`'s value indicates what the specified property's value shouldn't be.
+
+In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
+
+### Unique Database Index
+
+The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
+
+For example:
+
+```ts highlights={uniqueHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ unique: true,
+ },
+])
+
+export default MyCustom
+```
+
+This creates a unique composite index on the `name` and `age` properties.
+
+
+# Add Data Model Check Constraints
+
+In this chapter, you'll learn how to add check constraints to your data model.
+
+## What is a Check Constraint?
+
+A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
+
+For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
***
-## How to Pass Additional Data
+## How to Set a Check Constraint?
-### 1. Specify Validation of Additional Data
+To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
-Before passing custom data in the `additional_data` object parameter, you must specify validation rules for the allowed properties in the object.
+For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
-To do that, use the middleware route object defined in `src/api/middlewares.ts`.
+```ts highlights={checks1Highlights}
+import { model } from "@medusajs/framework/utils"
-For example, create the file `src/api/middlewares.ts` with the following content:
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ (columns) => `${columns.price} >= 0`,
+])
+```
-```ts title="src/api/middlewares.ts"
-import { defineMiddlewares } from "@medusajs/framework/http"
-import { z } from "zod"
+The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
-export default defineMiddlewares({
- routes: [
- {
- method: "POST",
- matcher: "/admin/products",
- additionalDataValidator: {
- brand: z.string().optional(),
- },
- },
+The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
+
+You can also pass an object to the `checks` method:
+
+```ts highlights={checks2Highlights}
+import { model } from "@medusajs/framework/utils"
+
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ {
+ name: "custom_product_price_check",
+ expression: (columns) => `${columns.price} >= 0`,
+ },
+])
+```
+
+The object accepts the following properties:
+
+- `name`: The check constraint's name.
+- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
+
+***
+
+## Apply in Migrations
+
+After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
+
+To generate a migration for the data model's module then reflect it on the database, run the following command:
+
+```bash
+npx medusa db:generate custom_module
+npx medusa db:migrate
+```
+
+The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
+
+
+# Infer Type of Data Model
+
+In this chapter, you'll learn how to infer the type of a data model.
+
+## How to Infer Type of Data Model?
+
+Consider you have a `Post` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable.
+
+Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
+
+For example:
+
+```ts
+import { InferTypeOf } from "@medusajs/framework/types"
+import { Post } from "../modules/blog/models/post" // relative path to the model
+
+export type Post = InferTypeOf
+```
+
+`InferTypeOf` accepts as a type argument the type of the data model.
+
+Since the `Post` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`.
+
+You can now use the `Post` type to reference a data model in other types, such as in workflow inputs or service method outputs:
+
+```ts title="Example Service"
+// other imports...
+import { InferTypeOf } from "@medusajs/framework/types"
+import { Post } from "../models/post"
+
+type Post = InferTypeOf
+
+class BlogModuleService extends MedusaService({ Post }) {
+ async doSomething(): Promise {
+ // ...
+ }
+}
+```
+
+
+# Manage Relationships
+
+In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
+
+## Manage One-to-One Relationship
+
+### BelongsTo Side of One-to-One
+
+When you create a record of a data model that belongs to another through a one-to-one relation, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property.
+
+For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set an email's user ID as follows:
+
+```ts highlights={belongsHighlights}
+// when creating an email
+const email = await helloModuleService.createEmails({
+ // other properties...
+ user_id: "123",
+})
+
+// when updating an email
+const email = await helloModuleService.updateEmails({
+ id: "321",
+ // other properties...
+ user_id: "123",
+})
+```
+
+In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to.
+
+### HasOne Side
+
+When you create a record of a data model that has one of another, pass the ID of the other data model's record in the relation property.
+
+For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set a user's email ID as follows:
+
+```ts highlights={hasOneHighlights}
+// when creating a user
+const user = await helloModuleService.createUsers({
+ // other properties...
+ email: "123",
+})
+
+// when updating a user
+const user = await helloModuleService.updateUsers({
+ id: "321",
+ // other properties...
+ email: "123",
+})
+```
+
+In the example above, you pass the `email` property when creating or updating a user to specify the email it has.
+
+***
+
+## Manage One-to-Many Relationship
+
+In a one-to-many relationship, you can only manage the associations from the `belongsTo` side.
+
+When you create a record of the data model on the `belongsTo` side, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property.
+
+For example, assuming you have the [Product and Store data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-many-relationship/index.html.md), set a product's store ID as follows:
+
+```ts highlights={manyBelongsHighlights}
+// when creating a product
+const product = await helloModuleService.createProducts({
+ // other properties...
+ store_id: "123",
+})
+
+// when updating a product
+const product = await helloModuleService.updateProducts({
+ id: "321",
+ // other properties...
+ store_id: "123",
+})
+```
+
+In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to.
+
+***
+
+## Manage Many-to-Many Relationship
+
+If your many-to-many relation is represented with a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship-with-pivotentity) instead.
+
+### Create Associations
+
+When you create a record of a data model that has a many-to-many relationship to another data model, pass an array of IDs of the other data model's records in the relation property.
+
+For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), set the association between products and orders as follows:
+
+```ts highlights={manyHighlights}
+// when creating a product
+const product = await helloModuleService.createProducts({
+ // other properties...
+ orders: ["123", "321"],
+})
+
+// when creating an order
+const order = await helloModuleService.createOrders({
+ id: "321",
+ // other properties...
+ products: ["123", "321"],
+})
+```
+
+In the example above, you pass the `orders` property when you create a product, and you pass the `products` property when you create an order.
+
+### Update Associations
+
+When you use the `update` methods generated by the service factory, you also pass an array of IDs as the relation property's value to add new associated records.
+
+However, this removes any existing associations to records whose IDs aren't included in the array.
+
+For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you update the product's related orders as so:
+
+```ts
+const product = await helloModuleService.updateProducts({
+ id: "123",
+ // other properties...
+ orders: ["321"],
+})
+```
+
+If the product was associated with an order, and you don't include that order's ID in the `orders` array, the association between the product and order is removed.
+
+So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product:
+
+```ts highlights={updateAssociationHighlights}
+const product = await helloModuleService.retrieveProduct(
+ "123",
+ {
+ relations: ["orders"],
+ }
+)
+
+const updatedProduct = await helloModuleService.updateProducts({
+ id: product.id,
+ // other properties...
+ orders: [
+ ...product.orders.map((order) => order.id),
+ "321",
],
})
```
-The middleware route object accepts an optional parameter `additionalDataValidator` whose value is an object of key-value pairs. The keys indicate the name of accepted properties in the `additional_data` parameter, and the value is [Zod](https://zod.dev/) validation rules of the property.
-
-In this example, you indicate that the `additional_data` parameter accepts a `brand` property whose value is an optional string.
-
-Refer to [Zod's documentation](https://zod.dev) for all available validation rules.
-
-### 2. Pass the Additional Data in a Request
-
-You can now pass a `brand` property in the `additional_data` parameter of a request to the Create Product API Route.
-
-For example:
-
-```bash
-curl -X POST 'http://localhost:9000/admin/products' \
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {token}' \
---data '{
- "title": "Product 1",
- "options": [
- {
- "title": "Default option",
- "values": ["Default option value"]
- }
- ],
- "shipping_profile_id": "{shipping_profile_id}",
- "additional_data": {
- "brand": "Acme"
- }
-}'
-```
-
-Make sure to replace the `{token}` in the authorization header with an admin user's authentication token, and `{shipping_profile_id}` with an existing shipping profile's ID.
-
-In this request, you pass in the `additional_data` parameter a `brand` property and set its value to `Acme`.
-
-The `additional_data` is then passed to hooks in the `createProductsWorkflow` used by the API route.
+This keeps existing associations between the product and orders, and adds a new one.
***
-## Use Additional Data in a Hook
+## Manage Many-to-Many Relationship with pivotEntity
-Learn about workflow hooks in [this guide](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
+If your many-to-many relation is represented without a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship) instead.
-Step functions consuming the workflow hook can access the `additional_data` in the first parameter.
+If you have a many-to-many relation with a `pivotEntity` specified, make sure to pass the data model representing the pivot table to [MedusaService](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that your module's service extends.
-For example, consider you want to store the data passed in `additional_data` in the product's `metadata` property.
+For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), add `OrderProduct` to `MedusaService`'s object parameter:
-To do that, create the file `src/workflows/hooks/product-created.ts` with the following content:
-
-```ts title="src/workflows/hooks/product-created.ts"
-import { StepResponse } from "@medusajs/framework/workflows-sdk"
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
-import { Modules } from "@medusajs/framework/utils"
-
-createProductsWorkflow.hooks.productsCreated(
- async ({ products, additional_data }, { container }) => {
- if (!additional_data?.brand) {
- return
- }
-
- const productModuleService = container.resolve(
- Modules.PRODUCT
- )
-
- await productModuleService.upsertProducts(
- products.map((product) => ({
- ...product,
- metadata: {
- ...product.metadata,
- brand: additional_data.brand,
- },
- }))
- )
-
- return new StepResponse(products, {
- products,
- additional_data,
- })
- }
-)
+```ts highlights={["4"]}
+class BlogModuleService extends MedusaService({
+ Order,
+ Product,
+ OrderProduct,
+}) {}
```
-This consumes the `productsCreated` hook, which runs after the products are created.
+This will generate Create, Read, Update and Delete (CRUD) methods for the `OrderProduct` data model, which you can use to create relations between orders and products and manage the extra columns in the pivot table.
-If `brand` is passed in `additional_data`, you resolve the Product Module's main service and use its `upsertProducts` method to update the products, adding the brand to the `metadata` property.
+For example:
-### Compensation Function
-
-Hooks also accept a compensation function as a second parameter to undo the actions made by the step function.
-
-For example, pass the following second parameter to the `productsCreated` hook:
-
-```ts title="src/workflows/hooks/product-created.ts"
-createProductsWorkflow.hooks.productsCreated(
- async ({ products, additional_data }, { container }) => {
- // ...
+```ts
+// create order-product association
+const orderProduct = await blogModuleService.createOrderProducts({
+ order_id: "123",
+ product_id: "123",
+ metadata: {
+ test: true,
},
- async ({ products, additional_data }, { container }) => {
- if (!additional_data.brand) {
- return
- }
+})
- const productModuleService = container.resolve(
- Modules.PRODUCT
- )
+// update order-product association
+const orderProduct = await blogModuleService.updateOrderProducts({
+ id: "123",
+ metadata: {
+ test: false,
+ },
+})
- await productModuleService.upsertProducts(
- products
- )
+// delete order-product association
+await blogModuleService.deleteOrderProducts("123")
+```
+
+Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`.
+
+Refer to the [service factory reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for a full list of generated methods and their usages.
+
+***
+
+## Retrieve Records of Relation
+
+The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options.
+
+To retrieve the records associated with a data model's records through a relationship, pass in the second parameter object a `relations` property whose value is an array of relationship names.
+
+For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you retrieve a product's orders as follows:
+
+```ts highlights={retrieveHighlights}
+const product = await blogModuleService.retrieveProducts(
+ "123",
+ {
+ relations: ["orders"],
}
)
```
-This updates the products to their original state before adding the brand to their `metadata` property.
+In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
+
+
+# Data Model Properties
+
+In this chapter, you'll learn about the different property types you can use in a data model and how to configure a data model's properties.
+
+## Data Model's Default Properties
+
+By default, Medusa creates the following properties for every data model:
+
+- `created_at`: A [dateTime](#dateTime) property that stores when a record of the data model was created.
+- `updated_at`: A [dateTime](#dateTime) property that stores when a record of the data model was updated.
+- `deleted_at`: A [dateTime](#dateTime) property that stores when a record of the data model was deleted. When you soft-delete a record, Medusa sets the `deleted_at` property to the current date.
+
+***
+
+## Property Types
+
+This section covers the different property types you can define in a data model's schema using the `model` methods.
+
+### id
+
+The `id` method defines an automatically generated string ID property. The generated ID is a unique string that has a mix of letters and numbers.
+
+For example:
+
+```ts highlights={idHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id(),
+ // ...
+})
+
+export default Post
+```
+
+### text
+
+The `text` method defines a string property.
+
+For example:
+
+```ts highlights={textHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ name: model.text(),
+ // ...
+})
+
+export default Post
+```
+
+### number
+
+The `number` method defines a number property.
+
+For example:
+
+```ts highlights={numberHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ age: model.number(),
+ // ...
+})
+
+export default Post
+```
+
+### float
+
+This property is only available after [Medusa v2.1.2](https://github.com/medusajs/medusa/releases/tag/v2.1.2).
+
+The `float` method defines a number property that allows for values with decimal places.
+
+Use this property type when it's less important to have high precision for numbers with large decimal places. Alternatively, for higher percision, use the [bigNumber property](#bignumber).
+
+For example:
+
+```ts highlights={floatHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ rating: model.float(),
+ // ...
+})
+
+export default Post
+```
+
+### bigNumber
+
+The `bigNumber` method defines a number property that expects large numbers, such as prices.
+
+Use this property type when it's important to have high precision for numbers with large decimal places. Alternatively, for less percision, use the [float property](#float).
+
+For example:
+
+```ts highlights={bigNumberHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ price: model.bigNumber(),
+ // ...
+})
+
+export default Post
+```
+
+### boolean
+
+The `boolean` method defines a boolean property.
+
+For example:
+
+```ts highlights={booleanHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ hasAccount: model.boolean(),
+ // ...
+})
+
+export default Post
+```
+
+### enum
+
+The `enum` method defines a property whose value can only be one of the specified values.
+
+For example:
+
+```ts highlights={enumHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ color: model.enum(["black", "white"]),
+ // ...
+})
+
+export default Post
+```
+
+The `enum` method accepts an array of possible string values.
+
+### dateTime
+
+The `dateTime` method defines a timestamp property.
+
+For example:
+
+```ts highlights={dateTimeHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ date_of_birth: model.dateTime(),
+ // ...
+})
+
+export default Post
+```
+
+### json
+
+The `json` method defines a property whose value is a stringified JSON object.
+
+For example:
+
+```ts highlights={jsonHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ metadata: model.json(),
+ // ...
+})
+
+export default Post
+```
+
+### array
+
+The `array` method defines an array of strings property.
+
+For example:
+
+```ts highlights={arrHightlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ names: model.array(),
+ // ...
+})
+
+export default Post
+```
+
+### Properties Reference
+
+Refer to the [Data Model Language (DML) reference](https://docs.medusajs.com/resources/references/data-model/index.html.md) for a full reference of the properties.
+
+***
+
+## Set Primary Key Property
+
+To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
+
+For example:
+
+```ts highlights={highlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id().primaryKey(),
+ // ...
+})
+
+export default Post
+```
+
+In the example above, the `id` property is defined as the data model's primary key.
+
+***
+
+## Property Default Value
+
+Use the `default` method on a property's definition to specify the default value of a property.
+
+For example:
+
+```ts highlights={defaultHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ color: model
+ .enum(["black", "white"])
+ .default("black"),
+ age: model
+ .number()
+ .default(0),
+ // ...
+})
+
+export default Post
+```
+
+In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
+
+***
+
+## Make Property Optional
+
+Use the `nullable` method to indicate that a property’s value can be `null`. This is useful when you want a property to be optional.
+
+For example:
+
+```ts highlights={nullableHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ price: model.bigNumber().nullable(),
+ // ...
+})
+
+export default Post
+```
+
+In the example above, the `price` property is configured to allow `null` values, making it optional.
+
+***
+
+## Unique Property
+
+The `unique` method indicates that a property’s value must be unique in the database through a unique index.
+
+For example:
+
+```ts highlights={uniqueHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const User = model.define("user", {
+ email: model.text().unique(),
+ // ...
+})
+
+export default User
+```
+
+In this example, multiple users can’t have the same email.
+
+***
+
+## Define Database Index on Property
+
+Use the `index` method on a property's definition to define a database index.
+
+For example:
+
+```ts highlights={dbIndexHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id().primaryKey(),
+ name: model.text().index(
+ "IDX_MY_CUSTOM_NAME"
+ ),
+})
+
+export default Post
+```
+
+The `index` method optionally accepts the name of the index as a parameter.
+
+In this example, you define an index on the `name` property.
+
+***
+
+## Define a Searchable Property
+
+Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
+
+When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
+
+Use the `searchable` method on a `text` property to indicate that it's searchable.
+
+For example:
+
+```ts highlights={searchableHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ title: model.text().searchable(),
+ // ...
+})
+
+export default Post
+```
+
+In this example, the `title` property is searchable.
+
+### Search Example
+
+If you pass a `q` filter to the `listPosts` method:
+
+```ts
+const posts = await blogModuleService.listPosts({
+ q: "New Products",
+})
+```
+
+This retrieves records that include `New Products` in their `title` property.
+
+
+# Data Model Relationships
+
+In this chapter, you’ll learn how to define relationships between data models in your module.
+
+## What is a Relationship Property?
+
+A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`.
+
+When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type.
+
+You want to create a relation between data models in the same module.
+
+You want to create a relationship between data models in different modules. Use module links instead.
+
+***
+
+## One-to-One Relationship
+
+A one-to-one relationship indicates that one record of a data model belongs to or is associated with another.
+
+To define a one-to-one relationship, create relationship properties in the data models using the following methods:
+
+1. `hasOne`: indicates that the model has one record of the specified model.
+2. `belongsTo`: indicates that the model belongs to one record of the specified model.
+
+For example:
+
+```ts highlights={oneToOneHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const User = model.define("user", {
+ id: model.id().primaryKey(),
+ email: model.hasOne(() => Email),
+})
+
+const Email = model.define("email", {
+ id: model.id().primaryKey(),
+ user: model.belongsTo(() => User, {
+ mappedBy: "email",
+ }),
+})
+```
+
+In the example above, a user has one email, and an email belongs to one user.
+
+The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model.
+
+The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model.
+
+### Optional Relationship
+
+To make the relationship optional on the `hasOne` or `belongsTo` side, use the `nullable` method on either property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#make-property-optional/index.html.md).
+
+### One-sided One-to-One Relationship
+
+If the one-to-one relationship is only defined on one side, pass `undefined` to the `mappedBy` property in the `belongsTo` method.
+
+For example:
+
+```ts highlights={oneToOneUndefinedHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const User = model.define("user", {
+ id: model.id().primaryKey(),
+})
+
+const Email = model.define("email", {
+ id: model.id().primaryKey(),
+ user: model.belongsTo(() => User, {
+ mappedBy: undefined,
+ }),
+})
+```
+
+### One-to-One Relationship in the Database
+
+When you generate the migrations of data models that have a one-to-one relationship, the migration adds to the table of the data model that has the `belongsTo` property:
+
+1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `email` table will have a `user_id` column.
+2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
+
+
+
+***
+
+## One-to-Many Relationship
+
+A one-to-many relationship indicates that one record of a data model has many records of another data model.
+
+To define a one-to-many relationship, create relationship properties in the data models using the following methods:
+
+1. `hasMany`: indicates that the model has more than one record of the specified model.
+2. `belongsTo`: indicates that the model belongs to one record of the specified model.
+
+For example:
+
+```ts highlights={oneToManyHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Store = model.define("store", {
+ id: model.id().primaryKey(),
+ products: model.hasMany(() => Product),
+})
+
+const Product = model.define("product", {
+ id: model.id().primaryKey(),
+ store: model.belongsTo(() => Store, {
+ mappedBy: "products",
+ }),
+})
+```
+
+In this example, a store has many products, but a product belongs to one store.
+
+### Optional Relationship
+
+To make the relationship optional on the `belongsTo` side, use the `nullable` method on the property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#make-property-optional/index.html.md).
+
+### One-to-Many Relationship in the Database
+
+When you generate the migrations of data models that have a one-to-many relationship, the migration adds to the table of the data model that has the `belongsTo` property:
+
+1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `product` table will have a `store_id` column.
+2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
+
+
+
+***
+
+## Many-to-Many Relationship
+
+A many-to-many relationship indicates that many records of a data model can be associated with many records of another data model.
+
+To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
+
+For example:
+
+```ts highlights={manyToManyHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const Order = model.define("order", {
+ id: model.id().primaryKey(),
+ products: model.manyToMany(() => Product, {
+ mappedBy: "orders",
+ pivotTable: "order_product",
+ joinColumn: "order_id",
+ inverseJoinColumn: "product_id",
+ }),
+})
+
+const Product = model.define("product", {
+ id: model.id().primaryKey(),
+ orders: model.manyToMany(() => Order, {
+ mappedBy: "products",
+ }),
+})
+```
+
+The `manyToMany` method accepts two parameters:
+
+1. A function that returns the associated data model.
+2. An object of optional configuration. Only one of the data models in the relation can define the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations, and it's considered the owner data model. The object can accept the following properties:
+ - `mappedBy`: The name of the relationship property in the other data model. If not set, the property's name is inferred from the associated data model's name.
+ - `pivotTable`: The name of the pivot table created in the database for the many-to-many relation. If not set, the pivot table is inferred by combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`.
+ - `joinColumn`: The name of the column in the pivot table that points to the owner model's primary key.
+ - `inverseJoinColumn`: The name of the column in the pivot table that points to the owned model's primary key.
+
+The `pivotTable`, `joinColumn`, and `inverseJoinColumn` properties are only available after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7).
+
+Following [Medusa v2.1.0](https://github.com/medusajs/medusa/releases/tag/v2.1.0), if `pivotTable`, `joinColumn`, and `inverseJoinColumn` aren't specified on either model, the owner is decided based on alphabetical order. So, in the example above, the `Order` data model would be the owner.
+
+In this example, an order is associated with many products, and a product is associated with many orders. Since the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations are defined on the order, it's considered the owner data model.
+
+### Many-to-Many Relationship in the Database
+
+When you generate the migrations of data models that have a many-to-many relationship, the migration adds a new pivot table. Its name is either the name you specify in the `pivotTable` configuration or the inferred name combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`.
+
+The pivot table has a column with the name `{data_model}_id` for each of the data model's tables. It also has foreign keys on each of these columns to their respective tables.
+
+The pivot table has columns with foreign keys pointing to the primary key of the associated tables. The column's name is either:
+
+- The value of the `joinColumn` configuration for the owner table, and the `inverseJoinColumn` configuration for the owned table;
+- Or the inferred name `{table_name}_id`.
+
+
+
+### Many-To-Many with Custom Columns
+
+To add custom columns to the pivot table between two data models having a many-to-many relationship, you must define a new data model that represents the pivot table.
+
+For example:
+
+```ts highlights={manyToManyColumnHighlights}
+import { model } from "@medusajs/framework/utils"
+
+export const Order = model.define("order_test", {
+ id: model.id().primaryKey(),
+ products: model.manyToMany(() => Product, {
+ pivotEntity: () => OrderProduct,
+ }),
+})
+
+export const Product = model.define("product_test", {
+ id: model.id().primaryKey(),
+ orders: model.manyToMany(() => Order),
+})
+
+export const OrderProduct = model.define("orders_products", {
+ id: model.id().primaryKey(),
+ order: model.belongsTo(() => Order, {
+ mappedBy: "products",
+ }),
+ product: model.belongsTo(() => Product, {
+ mappedBy: "orders",
+ }),
+ metadata: model.json().nullable(),
+})
+```
+
+The `Order` and `Product` data models have a many-to-many relationship. To add extra columns to the created pivot table, you pass a `pivotEntity` option to the `products` relation in `Order` (since `Order` is the owner). The value of `pivotEntity` is a function that returns the data model representing the pivot table.
+
+The `OrderProduct` model defines, aside from the ID, the following properties:
+
+- `order`: A relation that indicates this model belongs to the `Order` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Order` data model.
+- `product`: A relation that indicates this model belongs to the `Product` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Product` data model.
+- `metadata`: An extra column to add to the pivot table of type `json`. You can add other columns as well to the model.
+
+***
+
+## Set Relationship Name in the Other Model
+
+The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model.
+
+This is useful if the relationship property’s name is different from that of the associated data model.
+
+As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
+
+For example:
+
+```ts highlights={relationNameHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const User = model.define("user", {
+ id: model.id().primaryKey(),
+ email: model.hasOne(() => Email, {
+ mappedBy: "owner",
+ }),
+})
+
+const Email = model.define("email", {
+ id: model.id().primaryKey(),
+ owner: model.belongsTo(() => User, {
+ mappedBy: "email",
+ }),
+})
+```
+
+In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`.
+
+***
+
+## Cascades
+
+When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it.
+
+For example, if a store is deleted, its products should also be deleted.
+
+The `cascades` method used on a data model configures which child records an operation is cascaded to.
+
+For example:
+
+```ts highlights={highlights}
+import { model } from "@medusajs/framework/utils"
+
+const Store = model.define("store", {
+ id: model.id().primaryKey(),
+ products: model.hasMany(() => Product),
+})
+.cascades({
+ delete: ["products"],
+})
+
+const Product = model.define("product", {
+ id: model.id().primaryKey(),
+ store: model.belongsTo(() => Store, {
+ mappedBy: "products",
+ }),
+})
+```
+
+The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to.
+
+In the example above, when a store is deleted, its associated products are also deleted.
+
+
+# Migrations
+
+In this chapter, you'll learn what a migration is and how to generate a migration or write it manually.
+
+## What is a Migration?
+
+A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
+
+The migration's file has a class with two methods:
+
+- The `up` method reflects changes on the database.
+- The `down` method reverts the changes made in the `up` method.
+
+***
+
+## Generate Migration
+
+Instead of you writing the migration manually, the Medusa CLI tool provides a [db:generate](https://docs.medusajs.com/resources/medusa-cli/commands/db#dbgenerate/index.html.md) command to generate a migration for a modules' data models.
+
+For example, assuming you have a `blog` Module, you can generate a migration for it by running the following command:
+
+```bash
+npx medusa db:generate blog
+```
+
+This generates a migration file under the `migrations` directory of the Blog Module. You can then run it to reflect the changes in the database as mentioned in [this section](#run-the-migration).
+
+***
+
+## Write a Migration Manually
+
+You can also write migrations manually. To do that, create a file in the `migrations` directory of the module and in it, a class that has an `up` and `down` method. The class's name should be of the format `Migration{YEAR}{MONTH}{DAY}{HOUR}{MINUTE}.ts` to ensure migrations are ran in the correct order.
+
+For example:
+
+```ts title="src/modules/blog/migrations/Migration202507021059.ts"
+import { Migration } from "@mikro-orm/migrations"
+
+export class Migration202507021059 extends Migration {
+
+ async up(): Promise {
+ this.addSql("create table if not exists \"author\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"author_pkey\" primary key (\"id\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"author\" cascade;")
+ }
+
+}
+```
+
+The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
+
+In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted.
+
+Refer to [MikroORM's documentation](https://mikro-orm.io/docs/migrations#migration-class) for more details on writing migrations.
+
+***
+
+## Run the Migration
+
+To run your migration, run the following command:
+
+This command also syncs module links. If you don't want that, use the `--skip-links` option.
+
+```bash
+npx medusa db:migrate
+```
+
+This reflects the changes in the database as implemented in the migration's `up` method.
+
+***
+
+## Rollback the Migration
+
+To rollback or revert the last migration you ran for a module, run the following command:
+
+```bash
+npx medusa db:rollback blog
+```
+
+This rolls back the last ran migration on the Blog Module.
+
+### Caution: Rollback Migration before Deleting
+
+If you need to delete a migration file, make sure to rollback the migration first. Otherwise, you might encounter issues when generating and running new migrations.
+
+For example, if you delete the migration of the Blog Module, then try to create a new one, Medusa will create a brand new migration that re-creates the tables or indices. If those are still in the database, you might encounter errors.
+
+So, always rollback the migration before deleting it.
+
+***
+
+## More Database Commands
+
+To learn more about the Medusa CLI's database commands, refer to [this CLI reference](https://docs.medusajs.com/resources/medusa-cli/commands/db/index.html.md).
+
+
+# HTTP Methods
+
+In this chapter, you'll learn about how to add new API routes for each HTTP method.
+
+## HTTP Method Handler
+
+An API route is created for every HTTP method you export a handler function for in a route file.
+
+Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
+
+For example, create the file `src/api/hello-world/route.ts` with the following content:
+
+```ts title="src/api/hello-world/route.ts"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[GET] Hello world!",
+ })
+}
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[POST] Hello world!",
+ })
+}
+```
+
+This adds two API Routes:
+
+- A `GET` route at `http://localhost:9000/hello-world`.
+- A `POST` route at `http://localhost:9000/hello-world`.
# Handling CORS in API Routes
@@ -7578,49 +8797,6 @@ The `errorHandler` property's value is a function that accepts four parameters:
This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
-# HTTP Methods
-
-In this chapter, you'll learn about how to add new API routes for each HTTP method.
-
-## HTTP Method Handler
-
-An API route is created for every HTTP method you export a handler function for in a route file.
-
-Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
-
-For example, create the file `src/api/hello-world/route.ts` with the following content:
-
-```ts title="src/api/hello-world/route.ts"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[GET] Hello world!",
- })
-}
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[POST] Hello world!",
- })
-}
-```
-
-This adds two API Routes:
-
-- A `GET` route at `http://localhost:9000/hello-world`.
-- A `POST` route at `http://localhost:9000/hello-world`.
-
-
# Middlewares
In this chapter, you’ll learn about middlewares and how to create them.
@@ -8285,6 +9461,108 @@ export async function POST(
Check out the [uploadFilesWorkflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/uploadFilesWorkflow/index.html.md) for details on the expected input and output of the workflow.
+# API Route Response
+
+In this chapter, you'll learn how to send a response in your API route.
+
+## Send a JSON Response
+
+To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
+
+For example:
+
+```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "Hello, World!",
+ })
+}
+```
+
+This API route returns the following JSON object:
+
+```json
+{
+ "message": "Hello, World!"
+}
+```
+
+***
+
+## Set Response Status Code
+
+By default, setting the JSON data using the `json` method returns a response with a `200` status code.
+
+To change the status code, use the `status` method of the `MedusaResponse` object.
+
+For example:
+
+```ts title="src/api/custom/route.ts" highlights={statusHighlight}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.status(201).json({
+ message: "Hello, World!",
+ })
+}
+```
+
+The response of this API route has the status code `201`.
+
+***
+
+## Change Response Content Type
+
+To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
+
+For example, to create an API route that returns an event stream:
+
+```ts highlights={streamHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.writeHead(200, {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ })
+
+ const interval = setInterval(() => {
+ res.write("Streaming data...\n")
+ }, 3000)
+
+ req.on("end", () => {
+ clearInterval(interval)
+ res.end()
+ })
+}
+```
+
+The `writeHead` method accepts two parameters:
+
+1. The first one is the response's status code.
+2. The second is an object of key-value pairs to set the headers of the response.
+
+This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
+
+***
+
+## Do More with Responses
+
+The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses.
+
+
# Protected Routes
In this chapter, you’ll learn how to create protected routes.
@@ -8487,108 +9765,6 @@ export const GET = async (
In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
-# API Route Response
-
-In this chapter, you'll learn how to send a response in your API route.
-
-## Send a JSON Response
-
-To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
-
-For example:
-
-```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "Hello, World!",
- })
-}
-```
-
-This API route returns the following JSON object:
-
-```json
-{
- "message": "Hello, World!"
-}
-```
-
-***
-
-## Set Response Status Code
-
-By default, setting the JSON data using the `json` method returns a response with a `200` status code.
-
-To change the status code, use the `status` method of the `MedusaResponse` object.
-
-For example:
-
-```ts title="src/api/custom/route.ts" highlights={statusHighlight}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.status(201).json({
- message: "Hello, World!",
- })
-}
-```
-
-The response of this API route has the status code `201`.
-
-***
-
-## Change Response Content Type
-
-To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
-
-For example, to create an API route that returns an event stream:
-
-```ts highlights={streamHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.writeHead(200, {
- "Content-Type": "text/event-stream",
- "Cache-Control": "no-cache",
- Connection: "keep-alive",
- })
-
- const interval = setInterval(() => {
- res.write("Streaming data...\n")
- }, 3000)
-
- req.on("end", () => {
- clearInterval(interval)
- res.end()
- })
-}
-```
-
-The `writeHead` method accepts two parameters:
-
-1. The first one is the response's status code.
-2. The second is an object of key-value pairs to set the headers of the response.
-
-This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
-
-***
-
-## Do More with Responses
-
-The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses.
-
-
# Request Body and Query Parameter Validation
In this chapter, you'll learn how to validate request body and query parameters in your custom API route.
@@ -8838,1539 +10014,6 @@ For example, if you omit the `a` parameter, you'll receive a `400` response code
To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev).
-# Add Data Model Check Constraints
-
-In this chapter, you'll learn how to add check constraints to your data model.
-
-## What is a Check Constraint?
-
-A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
-
-For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
-
-***
-
-## How to Set a Check Constraint?
-
-To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
-
-For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
-
-```ts highlights={checks1Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- (columns) => `${columns.price} >= 0`,
-])
-```
-
-The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
-
-The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
-
-You can also pass an object to the `checks` method:
-
-```ts highlights={checks2Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- {
- name: "custom_product_price_check",
- expression: (columns) => `${columns.price} >= 0`,
- },
-])
-```
-
-The object accepts the following properties:
-
-- `name`: The check constraint's name.
-- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
-
-***
-
-## Apply in Migrations
-
-After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
-
-To generate a migration for the data model's module then reflect it on the database, run the following command:
-
-```bash
-npx medusa db:generate custom_module
-npx medusa db:migrate
-```
-
-The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
-
-
-# Seed Data with Custom CLI Script
-
-In this chapter, you'll learn how to seed data using a custom CLI script.
-
-## How to Seed Data
-
-To seed dummy data for development or demo purposes, use a custom CLI script.
-
-In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
-
-### Example: Seed Dummy Products
-
-In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-
-First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-
-```bash npm2yarn
-npm install --save-dev @faker-js/faker
-```
-
-Then, create the file `src/scripts/demo-products.ts` with the following content:
-
-```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import { ExecArgs } from "@medusajs/framework/types"
-import { faker } from "@faker-js/faker"
-import {
- ContainerRegistrationKeys,
- Modules,
- ProductStatus,
-} from "@medusajs/framework/utils"
-import {
- createInventoryLevelsWorkflow,
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-
-export default async function seedDummyProducts({
- container,
-}: ExecArgs) {
- const salesChannelModuleService = container.resolve(
- Modules.SALES_CHANNEL
- )
- const logger = container.resolve(
- ContainerRegistrationKeys.LOGGER
- )
- const query = container.resolve(
- ContainerRegistrationKeys.QUERY
- )
-
- const defaultSalesChannel = await salesChannelModuleService
- .listSalesChannels({
- name: "Default Sales Channel",
- })
-
- const sizeOptions = ["S", "M", "L", "XL"]
- const colorOptions = ["Black", "White"]
- const currency_code = "eur"
- const productsNum = 50
-
- // TODO seed products
-}
-```
-
-So far, in the script, you:
-
-- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
-- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
-- Initialize some default data to use when seeding the products next.
-
-Next, replace the `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const productsData = new Array(productsNum).fill(0).map((_, index) => {
- const title = faker.commerce.product() + "_" + index
- return {
- title,
- is_giftcard: true,
- description: faker.commerce.productDescription(),
- status: ProductStatus.PUBLISHED,
- options: [
- {
- title: "Size",
- values: sizeOptions,
- },
- {
- title: "Color",
- values: colorOptions,
- },
- ],
- images: [
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- ],
- variants: new Array(10).fill(0).map((_, variantIndex) => ({
- title: `${title} ${variantIndex}`,
- sku: `variant-${variantIndex}${index}`,
- prices: new Array(10).fill(0).map((_, priceIndex) => ({
- currency_code,
- amount: 10 * priceIndex,
- })),
- options: {
- Size: sizeOptions[Math.floor(Math.random() * 3)],
- },
- })),
- shipping_profile_id: "sp_123",
- sales_channels: [
- {
- id: defaultSalesChannel[0].id,
- },
- ],
- }
-})
-
-// TODO seed products
-```
-
-You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
-
-Then, replace the new `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const { result: products } = await createProductsWorkflow(container).run({
- input: {
- products: productsData,
- },
-})
-
-logger.info(`Seeded ${products.length} products.`)
-
-// TODO add inventory levels
-```
-
-You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
-
-Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-logger.info("Seeding inventory levels.")
-
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: ["id"],
-})
-
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: ["id"],
-})
-
-const inventoryLevels = inventoryItems.map((inventoryItem) => ({
- location_id: stockLocations[0].id,
- stocked_quantity: 1000000,
- inventory_item_id: inventoryItem.id,
-}))
-
-await createInventoryLevelsWorkflow(container).run({
- input: {
- inventory_levels: inventoryLevels,
- },
-})
-
-logger.info("Finished seeding inventory levels data.")
-```
-
-You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
-
-Then, you generate inventory levels for each inventory item, associating it with the first stock location.
-
-Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
-
-### Test Script
-
-To test out the script, run the following command in your project's directory:
-
-```bash
-npx medusa exec ./src/scripts/demo-products.ts
-```
-
-This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
-
-
-# Data Model Database Index
-
-In this chapter, you’ll learn how to define a database index on a data model.
-
-You can also define an index on a property as explained in the [Properties chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#define-database-index-on-property/index.html.md).
-
-## Define Database Index on Data Model
-
-A data model has an `indexes` method that defines database indices on its properties.
-
-The index can be on multiple columns (composite index). For example:
-
-```ts highlights={dataModelIndexHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- },
-])
-
-export default MyCustom
-```
-
-The `indexes` method receives an array of indices as a parameter. Each index is an object with a required `on` property indicating the properties to apply the index on.
-
-In the above example, you define a composite index on the `name` and `age` properties.
-
-### Index Conditions
-
-An index can have conditions. For example:
-
-```ts highlights={conditionHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- where: {
- age: 30,
- },
- },
-])
-
-export default MyCustom
-```
-
-The index object passed to `indexes` accepts a `where` property whose value is an object of conditions. The object's key is a property's name, and its value is the condition on that property.
-
-In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
-
-A property's condition can be a negation. For example:
-
-```ts highlights={negationHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number().nullable(),
-}).indexes([
- {
- on: ["name", "age"],
- where: {
- age: {
- $ne: null,
- },
- },
- },
-])
-
-export default MyCustom
-```
-
-A property's value in `where` can be an object having a `$ne` property. `$ne`'s value indicates what the specified property's value shouldn't be.
-
-In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
-
-### Unique Database Index
-
-The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
-
-For example:
-
-```ts highlights={uniqueHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- unique: true,
- },
-])
-
-export default MyCustom
-```
-
-This creates a unique composite index on the `name` and `age` properties.
-
-
-# Infer Type of Data Model
-
-In this chapter, you'll learn how to infer the type of a data model.
-
-## How to Infer Type of Data Model?
-
-Consider you have a `Post` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable.
-
-Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
-
-For example:
-
-```ts
-import { InferTypeOf } from "@medusajs/framework/types"
-import { Post } from "../modules/blog/models/post" // relative path to the model
-
-export type Post = InferTypeOf
-```
-
-`InferTypeOf` accepts as a type argument the type of the data model.
-
-Since the `Post` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`.
-
-You can now use the `Post` type to reference a data model in other types, such as in workflow inputs or service method outputs:
-
-```ts title="Example Service"
-// other imports...
-import { InferTypeOf } from "@medusajs/framework/types"
-import { Post } from "../models/post"
-
-type Post = InferTypeOf
-
-class BlogModuleService extends MedusaService({ Post }) {
- async doSomething(): Promise {
- // ...
- }
-}
-```
-
-
-# Data Model Properties
-
-In this chapter, you'll learn about the different property types you can use in a data model and how to configure a data model's properties.
-
-## Data Model's Default Properties
-
-By default, Medusa creates the following properties for every data model:
-
-- `created_at`: A [dateTime](#dateTime) property that stores when a record of the data model was created.
-- `updated_at`: A [dateTime](#dateTime) property that stores when a record of the data model was updated.
-- `deleted_at`: A [dateTime](#dateTime) property that stores when a record of the data model was deleted. When you soft-delete a record, Medusa sets the `deleted_at` property to the current date.
-
-***
-
-## Property Types
-
-This section covers the different property types you can define in a data model's schema using the `model` methods.
-
-### id
-
-The `id` method defines an automatically generated string ID property. The generated ID is a unique string that has a mix of letters and numbers.
-
-For example:
-
-```ts highlights={idHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- id: model.id(),
- // ...
-})
-
-export default Post
-```
-
-### text
-
-The `text` method defines a string property.
-
-For example:
-
-```ts highlights={textHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- name: model.text(),
- // ...
-})
-
-export default Post
-```
-
-### number
-
-The `number` method defines a number property.
-
-For example:
-
-```ts highlights={numberHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- age: model.number(),
- // ...
-})
-
-export default Post
-```
-
-### float
-
-This property is only available after [Medusa v2.1.2](https://github.com/medusajs/medusa/releases/tag/v2.1.2).
-
-The `float` method defines a number property that allows for values with decimal places.
-
-Use this property type when it's less important to have high precision for numbers with large decimal places. Alternatively, for higher percision, use the [bigNumber property](#bignumber).
-
-For example:
-
-```ts highlights={floatHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- rating: model.float(),
- // ...
-})
-
-export default Post
-```
-
-### bigNumber
-
-The `bigNumber` method defines a number property that expects large numbers, such as prices.
-
-Use this property type when it's important to have high precision for numbers with large decimal places. Alternatively, for less percision, use the [float property](#float).
-
-For example:
-
-```ts highlights={bigNumberHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- price: model.bigNumber(),
- // ...
-})
-
-export default Post
-```
-
-### boolean
-
-The `boolean` method defines a boolean property.
-
-For example:
-
-```ts highlights={booleanHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- hasAccount: model.boolean(),
- // ...
-})
-
-export default Post
-```
-
-### enum
-
-The `enum` method defines a property whose value can only be one of the specified values.
-
-For example:
-
-```ts highlights={enumHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- color: model.enum(["black", "white"]),
- // ...
-})
-
-export default Post
-```
-
-The `enum` method accepts an array of possible string values.
-
-### dateTime
-
-The `dateTime` method defines a timestamp property.
-
-For example:
-
-```ts highlights={dateTimeHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- date_of_birth: model.dateTime(),
- // ...
-})
-
-export default Post
-```
-
-### json
-
-The `json` method defines a property whose value is a stringified JSON object.
-
-For example:
-
-```ts highlights={jsonHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- metadata: model.json(),
- // ...
-})
-
-export default Post
-```
-
-### array
-
-The `array` method defines an array of strings property.
-
-For example:
-
-```ts highlights={arrHightlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- names: model.array(),
- // ...
-})
-
-export default Post
-```
-
-### Properties Reference
-
-Refer to the [Data Model Language (DML) reference](https://docs.medusajs.com/resources/references/data-model/index.html.md) for a full reference of the properties.
-
-***
-
-## Set Primary Key Property
-
-To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
-
-For example:
-
-```ts highlights={highlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- id: model.id().primaryKey(),
- // ...
-})
-
-export default Post
-```
-
-In the example above, the `id` property is defined as the data model's primary key.
-
-***
-
-## Property Default Value
-
-Use the `default` method on a property's definition to specify the default value of a property.
-
-For example:
-
-```ts highlights={defaultHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- color: model
- .enum(["black", "white"])
- .default("black"),
- age: model
- .number()
- .default(0),
- // ...
-})
-
-export default Post
-```
-
-In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
-
-***
-
-## Make Property Optional
-
-Use the `nullable` method to indicate that a property’s value can be `null`. This is useful when you want a property to be optional.
-
-For example:
-
-```ts highlights={nullableHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- price: model.bigNumber().nullable(),
- // ...
-})
-
-export default Post
-```
-
-In the example above, the `price` property is configured to allow `null` values, making it optional.
-
-***
-
-## Unique Property
-
-The `unique` method indicates that a property’s value must be unique in the database through a unique index.
-
-For example:
-
-```ts highlights={uniqueHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const User = model.define("user", {
- email: model.text().unique(),
- // ...
-})
-
-export default User
-```
-
-In this example, multiple users can’t have the same email.
-
-***
-
-## Define Database Index on Property
-
-Use the `index` method on a property's definition to define a database index.
-
-For example:
-
-```ts highlights={dbIndexHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- id: model.id().primaryKey(),
- name: model.text().index(
- "IDX_MY_CUSTOM_NAME"
- ),
-})
-
-export default Post
-```
-
-The `index` method optionally accepts the name of the index as a parameter.
-
-In this example, you define an index on the `name` property.
-
-***
-
-## Define a Searchable Property
-
-Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
-
-When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
-
-Use the `searchable` method on a `text` property to indicate that it's searchable.
-
-For example:
-
-```ts highlights={searchableHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Post = model.define("post", {
- title: model.text().searchable(),
- // ...
-})
-
-export default Post
-```
-
-In this example, the `title` property is searchable.
-
-### Search Example
-
-If you pass a `q` filter to the `listPosts` method:
-
-```ts
-const posts = await blogModuleService.listPosts({
- q: "New Products",
-})
-```
-
-This retrieves records that include `New Products` in their `title` property.
-
-
-# Manage Relationships
-
-In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
-
-## Manage One-to-One Relationship
-
-### BelongsTo Side of One-to-One
-
-When you create a record of a data model that belongs to another through a one-to-one relation, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property.
-
-For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set an email's user ID as follows:
-
-```ts highlights={belongsHighlights}
-// when creating an email
-const email = await helloModuleService.createEmails({
- // other properties...
- user_id: "123",
-})
-
-// when updating an email
-const email = await helloModuleService.updateEmails({
- id: "321",
- // other properties...
- user_id: "123",
-})
-```
-
-In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to.
-
-### HasOne Side
-
-When you create a record of a data model that has one of another, pass the ID of the other data model's record in the relation property.
-
-For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set a user's email ID as follows:
-
-```ts highlights={hasOneHighlights}
-// when creating a user
-const user = await helloModuleService.createUsers({
- // other properties...
- email: "123",
-})
-
-// when updating a user
-const user = await helloModuleService.updateUsers({
- id: "321",
- // other properties...
- email: "123",
-})
-```
-
-In the example above, you pass the `email` property when creating or updating a user to specify the email it has.
-
-***
-
-## Manage One-to-Many Relationship
-
-In a one-to-many relationship, you can only manage the associations from the `belongsTo` side.
-
-When you create a record of the data model on the `belongsTo` side, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property.
-
-For example, assuming you have the [Product and Store data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-many-relationship/index.html.md), set a product's store ID as follows:
-
-```ts highlights={manyBelongsHighlights}
-// when creating a product
-const product = await helloModuleService.createProducts({
- // other properties...
- store_id: "123",
-})
-
-// when updating a product
-const product = await helloModuleService.updateProducts({
- id: "321",
- // other properties...
- store_id: "123",
-})
-```
-
-In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to.
-
-***
-
-## Manage Many-to-Many Relationship
-
-If your many-to-many relation is represented with a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship-with-pivotentity) instead.
-
-### Create Associations
-
-When you create a record of a data model that has a many-to-many relationship to another data model, pass an array of IDs of the other data model's records in the relation property.
-
-For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), set the association between products and orders as follows:
-
-```ts highlights={manyHighlights}
-// when creating a product
-const product = await helloModuleService.createProducts({
- // other properties...
- orders: ["123", "321"],
-})
-
-// when creating an order
-const order = await helloModuleService.createOrders({
- id: "321",
- // other properties...
- products: ["123", "321"],
-})
-```
-
-In the example above, you pass the `orders` property when you create a product, and you pass the `products` property when you create an order.
-
-### Update Associations
-
-When you use the `update` methods generated by the service factory, you also pass an array of IDs as the relation property's value to add new associated records.
-
-However, this removes any existing associations to records whose IDs aren't included in the array.
-
-For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you update the product's related orders as so:
-
-```ts
-const product = await helloModuleService.updateProducts({
- id: "123",
- // other properties...
- orders: ["321"],
-})
-```
-
-If the product was associated with an order, and you don't include that order's ID in the `orders` array, the association between the product and order is removed.
-
-So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product:
-
-```ts highlights={updateAssociationHighlights}
-const product = await helloModuleService.retrieveProduct(
- "123",
- {
- relations: ["orders"],
- }
-)
-
-const updatedProduct = await helloModuleService.updateProducts({
- id: product.id,
- // other properties...
- orders: [
- ...product.orders.map((order) => order.id),
- "321",
- ],
-})
-```
-
-This keeps existing associations between the product and orders, and adds a new one.
-
-***
-
-## Manage Many-to-Many Relationship with pivotEntity
-
-If your many-to-many relation is represented without a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship) instead.
-
-If you have a many-to-many relation with a `pivotEntity` specified, make sure to pass the data model representing the pivot table to [MedusaService](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that your module's service extends.
-
-For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), add `OrderProduct` to `MedusaService`'s object parameter:
-
-```ts highlights={["4"]}
-class BlogModuleService extends MedusaService({
- Order,
- Product,
- OrderProduct,
-}) {}
-```
-
-This will generate Create, Read, Update and Delete (CRUD) methods for the `OrderProduct` data model, which you can use to create relations between orders and products and manage the extra columns in the pivot table.
-
-For example:
-
-```ts
-// create order-product association
-const orderProduct = await blogModuleService.createOrderProducts({
- order_id: "123",
- product_id: "123",
- metadata: {
- test: true,
- },
-})
-
-// update order-product association
-const orderProduct = await blogModuleService.updateOrderProducts({
- id: "123",
- metadata: {
- test: false,
- },
-})
-
-// delete order-product association
-await blogModuleService.deleteOrderProducts("123")
-```
-
-Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`.
-
-Refer to the [service factory reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for a full list of generated methods and their usages.
-
-***
-
-## Retrieve Records of Relation
-
-The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options.
-
-To retrieve the records associated with a data model's records through a relationship, pass in the second parameter object a `relations` property whose value is an array of relationship names.
-
-For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you retrieve a product's orders as follows:
-
-```ts highlights={retrieveHighlights}
-const product = await blogModuleService.retrieveProducts(
- "123",
- {
- relations: ["orders"],
- }
-)
-```
-
-In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
-
-
-# Data Model Relationships
-
-In this chapter, you’ll learn how to define relationships between data models in your module.
-
-## What is a Relationship Property?
-
-A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`.
-
-When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type.
-
-You want to create a relation between data models in the same module.
-
-You want to create a relationship between data models in different modules. Use module links instead.
-
-***
-
-## One-to-One Relationship
-
-A one-to-one relationship indicates that one record of a data model belongs to or is associated with another.
-
-To define a one-to-one relationship, create relationship properties in the data models using the following methods:
-
-1. `hasOne`: indicates that the model has one record of the specified model.
-2. `belongsTo`: indicates that the model belongs to one record of the specified model.
-
-For example:
-
-```ts highlights={oneToOneHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const User = model.define("user", {
- id: model.id().primaryKey(),
- email: model.hasOne(() => Email),
-})
-
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- user: model.belongsTo(() => User, {
- mappedBy: "email",
- }),
-})
-```
-
-In the example above, a user has one email, and an email belongs to one user.
-
-The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model.
-
-The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model.
-
-### Optional Relationship
-
-To make the relationship optional on the `hasOne` or `belongsTo` side, use the `nullable` method on either property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#make-property-optional/index.html.md).
-
-### One-sided One-to-One Relationship
-
-If the one-to-one relationship is only defined on one side, pass `undefined` to the `mappedBy` property in the `belongsTo` method.
-
-For example:
-
-```ts highlights={oneToOneUndefinedHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const User = model.define("user", {
- id: model.id().primaryKey(),
-})
-
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- user: model.belongsTo(() => User, {
- mappedBy: undefined,
- }),
-})
-```
-
-### One-to-One Relationship in the Database
-
-When you generate the migrations of data models that have a one-to-one relationship, the migration adds to the table of the data model that has the `belongsTo` property:
-
-1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `email` table will have a `user_id` column.
-2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
-
-
-
-***
-
-## One-to-Many Relationship
-
-A one-to-many relationship indicates that one record of a data model has many records of another data model.
-
-To define a one-to-many relationship, create relationship properties in the data models using the following methods:
-
-1. `hasMany`: indicates that the model has more than one record of the specified model.
-2. `belongsTo`: indicates that the model belongs to one record of the specified model.
-
-For example:
-
-```ts highlights={oneToManyHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Store = model.define("store", {
- id: model.id().primaryKey(),
- products: model.hasMany(() => Product),
-})
-
-const Product = model.define("product", {
- id: model.id().primaryKey(),
- store: model.belongsTo(() => Store, {
- mappedBy: "products",
- }),
-})
-```
-
-In this example, a store has many products, but a product belongs to one store.
-
-### Optional Relationship
-
-To make the relationship optional on the `belongsTo` side, use the `nullable` method on the property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/properties#make-property-optional/index.html.md).
-
-### One-to-Many Relationship in the Database
-
-When you generate the migrations of data models that have a one-to-many relationship, the migration adds to the table of the data model that has the `belongsTo` property:
-
-1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `product` table will have a `store_id` column.
-2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
-
-
-
-***
-
-## Many-to-Many Relationship
-
-A many-to-many relationship indicates that many records of a data model can be associated with many records of another data model.
-
-To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
-
-For example:
-
-```ts highlights={manyToManyHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const Order = model.define("order", {
- id: model.id().primaryKey(),
- products: model.manyToMany(() => Product, {
- mappedBy: "orders",
- pivotTable: "order_product",
- joinColumn: "order_id",
- inverseJoinColumn: "product_id",
- }),
-})
-
-const Product = model.define("product", {
- id: model.id().primaryKey(),
- orders: model.manyToMany(() => Order, {
- mappedBy: "products",
- }),
-})
-```
-
-The `manyToMany` method accepts two parameters:
-
-1. A function that returns the associated data model.
-2. An object of optional configuration. Only one of the data models in the relation can define the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations, and it's considered the owner data model. The object can accept the following properties:
- - `mappedBy`: The name of the relationship property in the other data model. If not set, the property's name is inferred from the associated data model's name.
- - `pivotTable`: The name of the pivot table created in the database for the many-to-many relation. If not set, the pivot table is inferred by combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`.
- - `joinColumn`: The name of the column in the pivot table that points to the owner model's primary key.
- - `inverseJoinColumn`: The name of the column in the pivot table that points to the owned model's primary key.
-
-The `pivotTable`, `joinColumn`, and `inverseJoinColumn` properties are only available after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7).
-
-Following [Medusa v2.1.0](https://github.com/medusajs/medusa/releases/tag/v2.1.0), if `pivotTable`, `joinColumn`, and `inverseJoinColumn` aren't specified on either model, the owner is decided based on alphabetical order. So, in the example above, the `Order` data model would be the owner.
-
-In this example, an order is associated with many products, and a product is associated with many orders. Since the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations are defined on the order, it's considered the owner data model.
-
-### Many-to-Many Relationship in the Database
-
-When you generate the migrations of data models that have a many-to-many relationship, the migration adds a new pivot table. Its name is either the name you specify in the `pivotTable` configuration or the inferred name combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`.
-
-The pivot table has a column with the name `{data_model}_id` for each of the data model's tables. It also has foreign keys on each of these columns to their respective tables.
-
-The pivot table has columns with foreign keys pointing to the primary key of the associated tables. The column's name is either:
-
-- The value of the `joinColumn` configuration for the owner table, and the `inverseJoinColumn` configuration for the owned table;
-- Or the inferred name `{table_name}_id`.
-
-
-
-### Many-To-Many with Custom Columns
-
-To add custom columns to the pivot table between two data models having a many-to-many relationship, you must define a new data model that represents the pivot table.
-
-For example:
-
-```ts highlights={manyToManyColumnHighlights}
-import { model } from "@medusajs/framework/utils"
-
-export const Order = model.define("order_test", {
- id: model.id().primaryKey(),
- products: model.manyToMany(() => Product, {
- pivotEntity: () => OrderProduct,
- }),
-})
-
-export const Product = model.define("product_test", {
- id: model.id().primaryKey(),
- orders: model.manyToMany(() => Order),
-})
-
-export const OrderProduct = model.define("orders_products", {
- id: model.id().primaryKey(),
- order: model.belongsTo(() => Order, {
- mappedBy: "products",
- }),
- product: model.belongsTo(() => Product, {
- mappedBy: "orders",
- }),
- metadata: model.json().nullable(),
-})
-```
-
-The `Order` and `Product` data models have a many-to-many relationship. To add extra columns to the created pivot table, you pass a `pivotEntity` option to the `products` relation in `Order` (since `Order` is the owner). The value of `pivotEntity` is a function that returns the data model representing the pivot table.
-
-The `OrderProduct` model defines, aside from the ID, the following properties:
-
-- `order`: A relation that indicates this model belongs to the `Order` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Order` data model.
-- `product`: A relation that indicates this model belongs to the `Product` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Product` data model.
-- `metadata`: An extra column to add to the pivot table of type `json`. You can add other columns as well to the model.
-
-***
-
-## Set Relationship Name in the Other Model
-
-The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model.
-
-This is useful if the relationship property’s name is different from that of the associated data model.
-
-As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
-
-For example:
-
-```ts highlights={relationNameHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const User = model.define("user", {
- id: model.id().primaryKey(),
- email: model.hasOne(() => Email, {
- mappedBy: "owner",
- }),
-})
-
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- owner: model.belongsTo(() => User, {
- mappedBy: "email",
- }),
-})
-```
-
-In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`.
-
-***
-
-## Cascades
-
-When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it.
-
-For example, if a store is deleted, its products should also be deleted.
-
-The `cascades` method used on a data model configures which child records an operation is cascaded to.
-
-For example:
-
-```ts highlights={highlights}
-import { model } from "@medusajs/framework/utils"
-
-const Store = model.define("store", {
- id: model.id().primaryKey(),
- products: model.hasMany(() => Product),
-})
-.cascades({
- delete: ["products"],
-})
-
-const Product = model.define("product", {
- id: model.id().primaryKey(),
- store: model.belongsTo(() => Store, {
- mappedBy: "products",
- }),
-})
-```
-
-The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to.
-
-In the example above, when a store is deleted, its associated products are also deleted.
-
-
-# Migrations
-
-In this chapter, you'll learn what a migration is and how to generate a migration or write it manually.
-
-## What is a Migration?
-
-A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations.
-
-The migration's file has a class with two methods:
-
-- The `up` method reflects changes on the database.
-- The `down` method reverts the changes made in the `up` method.
-
-***
-
-## Generate Migration
-
-Instead of you writing the migration manually, the Medusa CLI tool provides a [db:generate](https://docs.medusajs.com/resources/medusa-cli/commands/db#dbgenerate/index.html.md) command to generate a migration for a modules' data models.
-
-For example, assuming you have a `blog` Module, you can generate a migration for it by running the following command:
-
-```bash
-npx medusa db:generate blog
-```
-
-This generates a migration file under the `migrations` directory of the Blog Module. You can then run it to reflect the changes in the database as mentioned in [this section](#run-the-migration).
-
-***
-
-## Write a Migration Manually
-
-You can also write migrations manually. To do that, create a file in the `migrations` directory of the module and in it, a class that has an `up` and `down` method. The class's name should be of the format `Migration{YEAR}{MONTH}{DAY}{HOUR}{MINUTE}.ts` to ensure migrations are ran in the correct order.
-
-For example:
-
-```ts title="src/modules/blog/migrations/Migration202507021059.ts"
-import { Migration } from "@mikro-orm/migrations"
-
-export class Migration202507021059 extends Migration {
-
- async up(): Promise {
- this.addSql("create table if not exists \"author\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"author_pkey\" primary key (\"id\"));")
- }
-
- async down(): Promise {
- this.addSql("drop table if exists \"author\" cascade;")
- }
-
-}
-```
-
-The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
-
-In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted.
-
-Refer to [MikroORM's documentation](https://mikro-orm.io/docs/migrations#migration-class) for more details on writing migrations.
-
-***
-
-## Run the Migration
-
-To run your migration, run the following command:
-
-This command also syncs module links. If you don't want that, use the `--skip-links` option.
-
-```bash
-npx medusa db:migrate
-```
-
-This reflects the changes in the database as implemented in the migration's `up` method.
-
-***
-
-## Rollback the Migration
-
-To rollback or revert the last migration you ran for a module, run the following command:
-
-```bash
-npx medusa db:rollback blog
-```
-
-This rolls back the last ran migration on the Blog Module.
-
-### Caution: Rollback Migration before Deleting
-
-If you need to delete a migration file, make sure to rollback the migration first. Otherwise, you might encounter issues when generating and running new migrations.
-
-For example, if you delete the migration of the Blog Module, then try to create a new one, Medusa will create a brand new migration that re-creates the tables or indices. If those are still in the database, you might encounter errors.
-
-So, always rollback the migration before deleting it.
-
-***
-
-## More Database Commands
-
-To learn more about the Medusa CLI's database commands, refer to [this CLI reference](https://docs.medusajs.com/resources/medusa-cli/commands/db/index.html.md).
-
-
-# Add Columns to a Link Table
-
-In this chapter, you'll learn how to add custom columns to a link definition's table and manage them.
-
-## Link Table's Default Columns
-
-When you define a link between two data models, Medusa creates a link table in the database to store the IDs of the linked records. You can learn more about the created table in the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-In various cases, you might need to store additional data in the link table. For example, if you define a link between a `product` and a `post`, you might want to store the publish date of the product's post in the link table.
-
-In those cases, you can add a custom column to a link's table in the link definition. You can later set that column whenever you create or update a link between the linked records.
-
-***
-
-## How to Add Custom Columns to a Link's Table?
-
-The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
-
-To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
-
-```ts highlights={linkHighlights}
-import BlogModule from "../modules/blog"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.blog,
- {
- database: {
- extraColumns: {
- metadata: {
- type: "json",
- },
- },
- },
- }
-)
-```
-
-This adds to the table created for the link between `product` and `blog` a `metadata` column of type `json`.
-
-### Database Options
-
-The `database` property defines configuration for the table created in the database.
-
-Its `extraColumns` property defines custom columns to create in the link's table.
-
-`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
-
-### Column Configurations
-
-The column's configurations object accepts the following properties:
-
-- `type`: The column's type. Possible values are:
- - `string`
- - `text`
- - `integer`
- - `boolean`
- - `date`
- - `time`
- - `datetime`
- - `enum`
- - `json`
- - `array`
- - `enumArray`
- - `float`
- - `double`
- - `decimal`
- - `bigint`
- - `mediumint`
- - `smallint`
- - `tinyint`
- - `blob`
- - `uuid`
- - `uint8array`
-- `defaultValue`: The column's default value.
-- `nullable`: Whether the column can have `null` values.
-
-***
-
-## Set Custom Column when Creating Link
-
-The object you pass to Link's `create` method accepts a `data` property. Its value is an object whose keys are custom column names, and values are the value of the custom column for this link.
-
-For example:
-
-Learn more about Link, how to resolve it, and its methods in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
-
-```ts
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- [BLOG_MODULE]: {
- post_id: "321",
- },
- data: {
- metadata: {
- test: true,
- },
- },
-})
-```
-
-***
-
-## Retrieve Custom Column with Link
-
-To retrieve linked records with their custom columns, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
-
-For example:
-
-```ts highlights={retrieveHighlights}
-import productPostLink from "../links/product-post"
-
-// ...
-
-const { data } = await query.graph({
- entity: productPostLink.entryPoint,
- fields: ["metadata", "product.*", "post.*"],
- filters: {
- product_id: "prod_123",
- },
-})
-```
-
-This retrieves the product of id `prod_123` and its linked `post` records.
-
-In the `fields` array you pass `metadata`, which is the custom column to retrieve of the link.
-
-***
-
-## Update Custom Column's Value
-
-Link's `create` method updates a link's data if the link between the specified records already exists.
-
-So, to update the value of a custom column in a created link, use the `create` method again passing it a new value for the custom column.
-
-For example:
-
-```ts
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- [BLOG_MODULE]: {
- post_id: "321",
- },
- data: {
- metadata: {
- test: false,
- },
- },
-})
-```
-
-
# Event Data Payload
In this chapter, you'll learn how subscribers receive an event's data payload.
@@ -10416,67 +10059,6 @@ This logs the product ID received in the `product.created` event’s data payloa
Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */}
-# Module Link Direction
-
-In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
-
-The details in this chapter don't apply to [Read-Only Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md). Refer to the [Read-Only Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md) for more information on read-only links and their direction.
-
-## Link Direction
-
-The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
-
-For example, the following defines a link from the Blog Module's `post` data model to the Product Module's `product` data model:
-
-```ts
-export default defineLink(
- BlogModule.linkable.post,
- ProductModule.linkable.product
-)
-```
-
-Whereas the following defines a link from the Product Module's `product` data model to the Blog Module's `post` data model:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.post
-)
-```
-
-The above links are two different links that serve different purposes.
-
-***
-
-## Which Link Direction to Use?
-
-### Extend Data Models
-
-If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
-
-For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- BlogModule.linkable.subtitle
-)
-```
-
-### Associate Data Models
-
-If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
-
-For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
-
-```ts
-export default defineLink(
- BlogModule.linkable.post,
- ProductModule.linkable.product
-)
-```
-
-
# Emit Workflow and Service Events
In this chapter, you'll learn about event types and how to emit an event in a service or workflow.
@@ -10645,555 +10227,422 @@ If you execute the `performAction` method of your service, the event is emitted
Any subscribers listening to the event are also executed.
-# Query
+# Add Columns to a Link Table
-In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
+In this chapter, you'll learn how to add custom columns to a link definition's table and manage them.
-## What is Query?
+## Link Table's Default Columns
-Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
+When you define a link between two data models, Medusa creates a link table in the database to store the IDs of the linked records. You can learn more about the created table in the [Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-In all resources that can access the [Medusa Container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
+In various cases, you might need to store additional data in the link table. For example, if you define a link between a `product` and a `post`, you might want to store the publish date of the product's post in the link table.
+
+In those cases, you can add a custom column to a link's table in the link definition. You can later set that column whenever you create or update a link between the linked records.
***
-## Query Example
+## How to Add Custom Columns to a Link's Table?
-For example, create the route `src/api/query/route.ts` with the following content:
+The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
-```ts title="src/api/query/route.ts" highlights={exampleHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
+To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+```ts highlights={linkHighlights}
+import BlogModule from "../modules/blog"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
- const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- })
-
- res.json({ posts })
-}
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.blog,
+ {
+ database: {
+ extraColumns: {
+ metadata: {
+ type: "json",
+ },
+ },
+ },
+ }
+)
```
-In the above example, you resolve Query from the Medusa container using the `ContainerRegistrationKeys.QUERY` (`query`) key.
+This adds to the table created for the link between `product` and `blog` a `metadata` column of type `json`.
-Then, you run a query using its `graph` method. This method accepts as a parameter an object with the following required properties:
+### Database Options
-- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
-- `fields`: An array of the data model’s properties to retrieve in the result.
+The `database` property defines configuration for the table created in the database.
-The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
+Its `extraColumns` property defines custom columns to create in the link's table.
-```json title="Returned Data"
-{
- "data": [
- {
- "id": "123",
- "title": "My Post"
- }
- ]
-}
-```
+`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
+
+### Column Configurations
+
+The column's configurations object accepts the following properties:
+
+- `type`: The column's type. Possible values are:
+ - `string`
+ - `text`
+ - `integer`
+ - `boolean`
+ - `date`
+ - `time`
+ - `datetime`
+ - `enum`
+ - `json`
+ - `array`
+ - `enumArray`
+ - `float`
+ - `double`
+ - `decimal`
+ - `bigint`
+ - `mediumint`
+ - `smallint`
+ - `tinyint`
+ - `blob`
+ - `uuid`
+ - `uint8array`
+- `defaultValue`: The column's default value.
+- `nullable`: Whether the column can have `null` values.
***
-## Querying the Graph
+## Set Custom Column when Creating Link
-When you use the `query.graph` method, you're running a query through an internal graph that the Medusa application creates.
-
-This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.
-
-***
-
-## Retrieve Linked Records
-
-Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
+The object you pass to Link's `create` method accepts a `data` property. Its value is an object whose keys are custom column names, and values are the value of the custom column for this link.
For example:
-```ts highlights={[["6"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: [
- "id",
- "title",
- "product.*",
- ],
-})
-```
-
-`.*` means that all of data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name, for each property.
-
-For example:
+Learn more about Link, how to resolve it, and its methods in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
```ts
-const { data: posts } = await query.graph({
- entity: "post",
- fields: [
- "id",
- "title",
- "product.id",
- "product.title",
- ],
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "123",
+ },
+ [BLOG_MODULE]: {
+ post_id: "321",
+ },
+ data: {
+ metadata: {
+ test: true,
+ },
+ },
})
```
-In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`.
+***
-### Retrieve List Link Records
+## Retrieve Custom Column with Link
-If the linked data model has `isList` enabled in the link definition, pass in `fields` the data model's plural name suffixed with `.*`.
+To retrieve linked records with their custom columns, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
For example:
-```ts highlights={[["6"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: [
- "id",
- "title",
- "products.*",
- ],
-})
-```
-
-In the example above, you retrieve all products linked to a post.
-
-### Apply Filters and Pagination on Linked Records
-
-Consider you want to apply filters or pagination configurations on the product(s) linked to `post`. To do that, you must query the module link's table instead.
-
-As mentioned in the [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.
-
-A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
-
-For example:
-
-```ts highlights={queryLinkTableHighlights}
-import ProductPostLink from "../../../links/product-post"
+```ts highlights={retrieveHighlights}
+import productPostLink from "../links/product-post"
// ...
-const { data: productCustoms } = await query.graph({
- entity: ProductPostLink.entryPoint,
- fields: ["*", "product.*", "post.*"],
- pagination: {
- take: 5,
- skip: 0,
+const { data } = await query.graph({
+ entity: productPostLink.entryPoint,
+ fields: ["metadata", "product.*", "post.*"],
+ filters: {
+ product_id: "prod_123",
},
})
```
-In the object passed to the `graph` method:
+This retrieves the product of id `prod_123` and its linked `post` records.
-- You pass the `entryPoint` property of the link definition as the value for `entity`. So, Query will retrieve records from the module link's table.
-- You pass three items to the `field` property:
- - `*` to retrieve the link table's fields. This is useful if the link table has [custom columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
- - `product.*` to retrieve the fields of a product record linked to a `Post` record.
- - `post.*` to retrieve the fields of a `Post` record linked to a product record.
-
-You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
-
-The returned `data` is similar to the following:
-
-```json title="Example Result"
-[{
- "id": "123",
- "product_id": "prod_123",
- "post_id": "123",
- "product": {
- "id": "prod_123",
- // other product fields...
- },
- "post": {
- "id": "123",
- // other post fields...
- }
-}]
-```
+In the `fields` array you pass `metadata`, which is the custom column to retrieve of the link.
***
-## Apply Filters
+## Update Custom Column's Value
-```ts highlights={[["4"], ["5"], ["6"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- id: "post_123",
- },
-})
-```
+Link's `create` method updates a link's data if the link between the specified records already exists.
-The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
-
-In the example above, you filter the `post` records by the ID `post_123`.
-
-You can also filter by multiple values of a property. For example:
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- id: [
- "post_123",
- "post_321",
- ],
- },
-})
-```
-
-In the example above, you filter the `post` records by multiple IDs.
-
-Filters don't apply on fields of linked data models from other modules. Refer to the [Retrieve Linked Records](#retrieve-linked-records) section for an alternative solution.
-
-### Advanced Query Filters
-
-Under the hood, Query uses the `listX` (`listPosts`) method of the data model's module's service to retrieve records. This method accepts a filter object that can be used to filter records.
-
-Those filters don't just allow you to filter by exact values. You can also filter by properties that don't match a value, match multiple values, and other filter types.
-
-Refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/tips/filtering/index.html.md) for examples of advanced filters. The following sections provide some quick examples.
-
-#### Filter by Not Matching a Value
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- title: {
- $ne: null,
- },
- },
-})
-```
-
-In the example above, only posts that have a title are retrieved.
-
-#### Filter by Not Matching Multiple Values
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- title: {
- $nin: ["My Post", "Another Post"],
- },
- },
-})
-```
-
-In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved.
-
-#### Filter by a Range
-
-```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]}
-const startToday = new Date()
-startToday.setHours(0, 0, 0, 0)
-
-const endToday = new Date()
-endToday.setHours(23, 59, 59, 59)
-
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- published_at: {
- $gt: startToday,
- $lt: endToday,
- },
- },
-})
-```
-
-In the example above, only posts that were published today are retrieved.
-
-#### Filter Text by Like Value
-
-This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- title: {
- $like: "%My%",
- },
- },
-})
-```
-
-In the example above, only posts that have the word `My` in their title are retrieved.
-
-#### Filter a Relation's Property
-
-```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- author: {
- name: "John",
- },
- },
-})
-```
-
-While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module).
-
-In the example above, only posts that have an author with the name `John` are retrieved.
-
-***
-
-## Apply Pagination
-
-```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
-const {
- data: posts,
- metadata: { count, take, skip } = {},
-} = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- pagination: {
- skip: 0,
- take: 10,
- },
-})
-```
-
-The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
-
-To paginate the returned records, pass the following properties to `pagination`:
-
-- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
-- `take`: The number of records to fetch.
-
-When you provide the pagination fields, the `query.graph` method's returned object has a `metadata` property. Its value is an object having the following properties:
-
-- skip: (\`number\`) The number of records skipped.
-- take: (\`number\`) The number of records requested to fetch.
-- count: (\`number\`) The total number of records.
-
-### Sort Records
-
-```ts highlights={[["5"], ["6"], ["7"]]}
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- pagination: {
- order: {
- name: "DESC",
- },
- },
-})
-```
-
-Sorting doesn't work on fields of linked data models from other modules.
-
-To sort returned records, pass an `order` property to `pagination`.
-
-The `order` property is an object whose keys are property names, and values are either:
-
-- `ASC` to sort records by that property in ascending order.
-- `DESC` to sort records by that property in descending order.
-
-***
-
-## Configure Query to Throw Errors
-
-By default, if Query doesn't find records matching your query, it returns an empty array. You can add option to configure Query to throw an error when no records are found.
-
-The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`.
+So, to update the value of a custom column in a created link, use the `create` method again passing it a new value for the custom column.
For example:
```ts
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title"],
- filters: {
- id: "post_123",
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "123",
},
-}, {
- throwIfKeyNotFound: true,
-})
-```
-
-In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist.
-
-### Throw Error on Related Data Model
-
-The `throwIfKeyNotFound` option can also be used to throw an error if the ID of a related data model's record (in the same module) is passed in the filters, and the related record doesn't exist.
-
-For example:
-
-```ts
-const { data: posts } = await query.graph({
- entity: "post",
- fields: ["id", "title", "author.*"],
- filters: {
- id: "post_123",
- author_id: "author_123",
+ [BLOG_MODULE]: {
+ post_id: "321",
},
-}, {
- throwIfKeyNotFound: true,
-})
-```
-
-In the example above, Query throws an error either if no post is found with the ID `post_123` or if its found but its author ID isn't `author_123`.
-
-In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts.
-
-For example:
-
-```ts
-const { data: posts } = await query.graph({
- entity: "author",
- fields: ["id", "name", "posts.*"],
- filters: {
- id: "author_123",
- posts: {
- id: "post_123",
+ data: {
+ metadata: {
+ test: false,
},
},
-}, {
- throwIfKeyNotFound: true,
})
```
-In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`.
+
+# Pass Additional Data to Medusa's API Route
+
+In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.
+
+## Why Pass Additional Data?
+
+Some of Medusa's API Routes accept an `additional_data` parameter whose type is an object. The API Route passes the `additional_data` to the workflow, which in turn passes it to its hooks.
+
+This is useful when you have a link from your custom module to a commerce module, and you want to perform an additional action when a request is sent to an existing API route.
+
+For example, the [Create Product API Route](https://docs.medusajs.com/api/admin#products_postproducts) accepts an `additional_data` parameter. If you have a data model linked to it, you consume the `productsCreated` hook to create a record of the data model using the custom data and link it to the product.
+
+### API Routes Accepting Additional Data
+
+### API Routes List
+
+- Campaigns
+ - [Create Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaigns)
+ - [Update Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaignsid)
+- Cart
+ - [Create Cart](https://docs.medusajs.com/api/store#carts_postcarts)
+ - [Update Cart](https://docs.medusajs.com/api/store#carts_postcartsid)
+- Collections
+ - [Create Collection](https://docs.medusajs.com/api/admin#collections_postcollections)
+ - [Update Collection](https://docs.medusajs.com/api/admin#collections_postcollectionsid)
+- Customers
+ - [Create Customer](https://docs.medusajs.com/api/admin#customers_postcustomers)
+ - [Update Customer](https://docs.medusajs.com/api/admin#customers_postcustomersid)
+ - [Create Address](https://docs.medusajs.com/api/admin#customers_postcustomersidaddresses)
+ - [Update Address](https://docs.medusajs.com/api/admin#customers_postcustomersidaddressesaddress_id)
+- Draft Orders
+ - [Create Draft Order](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders)
+- Orders
+ - [Complete Orders](https://docs.medusajs.com/api/admin#orders_postordersidcomplete)
+ - [Cancel Order's Fulfillment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idcancel)
+ - [Create Shipment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idshipments)
+ - [Create Fulfillment](https://docs.medusajs.com/api/admin#orders_postordersidfulfillments)
+- Products
+ - [Create Product](https://docs.medusajs.com/api/admin#products_postproducts)
+ - [Update Product](https://docs.medusajs.com/api/admin#products_postproductsid)
+ - [Create Product Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariants)
+ - [Update Product Variant](https://docs.medusajs.com/api/admin#products_postproductsidvariantsvariant_id)
+ - [Create Product Option](https://docs.medusajs.com/api/admin#products_postproductsidoptions)
+ - [Update Product Option](https://docs.medusajs.com/api/admin#products_postproductsidoptionsoption_id)
+- Product Tags
+ - [Create Product Tag](https://docs.medusajs.com/api/admin#product-tags_postproducttags)
+ - [Update Product Tag](https://docs.medusajs.com/api/admin#product-tags_postproducttagsid)
+- Product Types
+ - [Create Product Type](https://docs.medusajs.com/api/admin#product-types_postproducttypes)
+ - [Update Product Type](https://docs.medusajs.com/api/admin#product-types_postproducttypesid)
+- Promotions
+ - [Create Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotions)
+ - [Update Promotion](https://docs.medusajs.com/api/admin#promotions_postpromotionsid)
***
-## Request Query Configurations
+## How to Pass Additional Data
-For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
+### 1. Specify Validation of Additional Data
-- Validates accepted query parameters, as explained in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
-- Parses configurations that are received as query parameters to be passed to Query.
+Before passing custom data in the `additional_data` object parameter, you must specify validation rules for the allowed properties in the object.
-Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
+To do that, use the middleware route object defined in `src/api/middlewares.ts`.
-### Step 1: Add Middleware
-
-The first step is to use the `validateAndTransformQuery` middleware on the `GET` route. You add the middleware in `src/api/middlewares.ts`:
+For example, create the file `src/api/middlewares.ts` with the following content:
```ts title="src/api/middlewares.ts"
-import {
- validateAndTransformQuery,
- defineMiddlewares,
-} from "@medusajs/framework/http"
-import { createFindParams } from "@medusajs/medusa/api/utils/validators"
-
-export const GetCustomSchema = createFindParams()
+import { defineMiddlewares } from "@medusajs/framework/http"
+import { z } from "zod"
export default defineMiddlewares({
routes: [
{
- matcher: "/customs",
- method: "GET",
- middlewares: [
- validateAndTransformQuery(
- GetCustomSchema,
- {
- defaults: [
- "id",
- "title",
- "products.*",
- ],
- isList: true,
- }
- ),
- ],
+ method: "POST",
+ matcher: "/admin/products",
+ additionalDataValidator: {
+ brand: z.string().optional(),
+ },
},
],
})
```
-The `validateAndTransformQuery` accepts two parameters:
+The middleware route object accepts an optional parameter `additionalDataValidator` whose value is an object of key-value pairs. The keys indicate the name of accepted properties in the `additional_data` parameter, and the value is [Zod](https://zod.dev/) validation rules of the property.
-1. A Zod validation schema for the query parameters, which you can learn more about in the [API Route Validation documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md). Medusa has a `createFindParams` utility that generates a Zod schema that accepts four query parameters:
- 1. `fields`: The fields and relations to retrieve in the returned resources.
- 2. `offset`: The number of items to skip before retrieving the returned items.
- 3. `limit`: The maximum number of items to return.
- 4. `order`: The fields to order the returned items by in ascending or descending order.
-2. A Query configuration object. It accepts the following properties:
- 1. `defaults`: An array of default fields and relations to retrieve in each resource.
- 2. `isList`: A boolean indicating whether a list of items are returned in the response.
- 3. `allowed`: An array of fields and relations allowed to be passed in the `fields` query parameter.
- 4. `defaultLimit`: A number indicating the default limit to use if no limit is provided. By default, it's `50`.
+In this example, you indicate that the `additional_data` parameter accepts a `brand` property whose value is an optional string.
-### Step 2: Use Configurations in API Route
+Refer to [Zod's documentation](https://zod.dev) for all available validation rules.
-After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
+### 2. Pass the Additional Data in a Request
-The middleware transforms these parameters to configurations that you can pass to Query in your API route handler. These configurations are stored in the `queryConfig` parameter of the `MedusaRequest` object.
+You can now pass a `brand` property in the `additional_data` parameter of a request to the Create Product API Route.
-As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), `remoteQueryConfig` has been deprecated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
+For example:
-For example, Create the file `src/api/customs/route.ts` with the following content:
-
-```ts title="src/api/customs/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: posts } = await query.graph({
- entity: "post",
- ...req.queryConfig,
- })
-
- res.json({ posts: posts })
-}
-```
-
-This adds a `GET` API route at `/customs`, which is the API route you added the middleware for.
-
-In the API route, you pass `req.queryConfig` to `query.graph`. `queryConfig` has properties like `fields` and `pagination` to configure the query based on the default values you specified in the middleware, and the query parameters passed in the request.
-
-### Test it Out
-
-To test it out, start your Medusa application and send a `GET` request to the `/customs` API route. A list of records are retrieved with the specified fields in the middleware.
-
-```json title="Returned Data"
-{
- "posts": [
- {
- "id": "123",
- "title": "test"
+```bash
+curl -X POST 'http://localhost:9000/admin/products' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "title": "Product 1",
+ "options": [
+ {
+ "title": "Default option",
+ "values": ["Default option value"]
+ }
+ ],
+ "shipping_profile_id": "{shipping_profile_id}",
+ "additional_data": {
+ "brand": "Acme"
}
- ]
-}
+}'
```
-Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
+Make sure to replace the `{token}` in the authorization header with an admin user's authentication token, and `{shipping_profile_id}` with an existing shipping profile's ID.
-Learn more about [specifing fields and relations](https://docs.medusajs.com/api/store#select-fields-and-relations) and [pagination](https://docs.medusajs.com/api/store#pagination) in the API reference.
+In this request, you pass in the `additional_data` parameter a `brand` property and set its value to `Acme`.
+
+The `additional_data` is then passed to hooks in the `createProductsWorkflow` used by the API route.
+
+***
+
+## Use Additional Data in a Hook
+
+Learn about workflow hooks in [this guide](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
+
+Step functions consuming the workflow hook can access the `additional_data` in the first parameter.
+
+For example, consider you want to store the data passed in `additional_data` in the product's `metadata` property.
+
+To do that, create the file `src/workflows/hooks/product-created.ts` with the following content:
+
+```ts title="src/workflows/hooks/product-created.ts"
+import { StepResponse } from "@medusajs/framework/workflows-sdk"
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+import { Modules } from "@medusajs/framework/utils"
+
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products, additional_data }, { container }) => {
+ if (!additional_data?.brand) {
+ return
+ }
+
+ const productModuleService = container.resolve(
+ Modules.PRODUCT
+ )
+
+ await productModuleService.upsertProducts(
+ products.map((product) => ({
+ ...product,
+ metadata: {
+ ...product.metadata,
+ brand: additional_data.brand,
+ },
+ }))
+ )
+
+ return new StepResponse(products, {
+ products,
+ additional_data,
+ })
+ }
+)
+```
+
+This consumes the `productsCreated` hook, which runs after the products are created.
+
+If `brand` is passed in `additional_data`, you resolve the Product Module's main service and use its `upsertProducts` method to update the products, adding the brand to the `metadata` property.
+
+### Compensation Function
+
+Hooks also accept a compensation function as a second parameter to undo the actions made by the step function.
+
+For example, pass the following second parameter to the `productsCreated` hook:
+
+```ts title="src/workflows/hooks/product-created.ts"
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products, additional_data }, { container }) => {
+ // ...
+ },
+ async ({ products, additional_data }, { container }) => {
+ if (!additional_data.brand) {
+ return
+ }
+
+ const productModuleService = container.resolve(
+ Modules.PRODUCT
+ )
+
+ await productModuleService.upsertProducts(
+ products
+ )
+ }
+)
+```
+
+This updates the products to their original state before adding the brand to their `metadata` property.
+
+
+# Module Link Direction
+
+In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
+
+The details in this chapter don't apply to [Read-Only Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md). Refer to the [Read-Only Module Links chapter](https://docs.medusajs.com/learn/fundamentals/module-links/read-only/index.html.md) for more information on read-only links and their direction.
+
+## Link Direction
+
+The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
+
+For example, the following defines a link from the Blog Module's `post` data model to the Product Module's `product` data model:
+
+```ts
+export default defineLink(
+ BlogModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
+
+Whereas the following defines a link from the Product Module's `product` data model to the Blog Module's `post` data model:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.post
+)
+```
+
+The above links are two different links that serve different purposes.
+
+***
+
+## Which Link Direction to Use?
+
+### Extend Data Models
+
+If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
+
+For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ BlogModule.linkable.subtitle
+)
+```
+
+### Associate Data Models
+
+If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
+
+For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
+
+```ts
+export default defineLink(
+ BlogModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
# Link
@@ -12154,18 +11603,33 @@ For example:
### Package Keywords
-In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
+Medusa has an Integrations listing that scrapes NPM for a list of plugins that integrate third-party services. If you want your plugin to appear in that listing, make sure to add the `medusa-v2` and `medusa-plugin-integration` keywords to the `keywords` field in `package.json`.
```json title="package.json"
{
"keywords": [
- "medusa-plugin",
+ "medusa-plugin-integration",
"medusa-v2"
],
// ...
}
```
+In addition, make sure to use one of the following keywords based on your integration type:
+
+|Keyword|Description|Example|
+|---|---|---|
+|\`medusa-plugin-analytics\`|Analytics service integration|Google Analytics|
+|\`medusa-plugin-auth\`|Authentication service integration|Auth0|
+|\`medusa-plugin-cms\`|CMS service integration|Contentful|
+|\`medusa-plugin-notification\`|Notification service integration|Twilio SMS|
+|\`medusa-plugin-payment\`|Payment service integration|PayPal|
+|\`medusa-plugin-search\`|Search service integration|MeiliSearch|
+|\`medusa-plugin-shipping\`|Shipping service integration|DHL|
+|\`medusa-plugin-storage\`|Storage or File service integration|Cloudinary|
+|\`medusa-plugin-source\`|Data migration integration|Shopify|
+|\`medusa-plugin-other\`|Other service integrations|Custom API Integration|
+
### Package Dependencies
Your plugin project will already have the dependencies mentioned in this section. Unless you made changes to the dependencies, you can skip this section.
@@ -12460,6 +11924,8 @@ To learn how to create module providers, refer to the following guides:
## 5. Publish Plugin to NPM
+Make sure to add the keywords mentioned in the [Package Keywords](#package-keywords) section in your plugin's `package.json` file.
+
Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
```bash
@@ -12514,6 +11980,557 @@ npm publish
This will publish an updated version of your plugin under a new version.
+# Query
+
+In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
+
+## What is Query?
+
+Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
+
+In all resources that can access the [Medusa Container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
+
+***
+
+## Query Example
+
+For example, create the route `src/api/query/route.ts` with the following content:
+
+```ts title="src/api/query/route.ts" highlights={exampleHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ })
+
+ res.json({ posts })
+}
+```
+
+In the above example, you resolve Query from the Medusa container using the `ContainerRegistrationKeys.QUERY` (`query`) key.
+
+Then, you run a query using its `graph` method. This method accepts as a parameter an object with the following required properties:
+
+- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
+- `fields`: An array of the data model’s properties to retrieve in the result.
+
+The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
+
+```json title="Returned Data"
+{
+ "data": [
+ {
+ "id": "123",
+ "title": "My Post"
+ }
+ ]
+}
+```
+
+***
+
+## Querying the Graph
+
+When you use the `query.graph` method, you're running a query through an internal graph that the Medusa application creates.
+
+This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.
+
+***
+
+## Retrieve Linked Records
+
+Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
+
+For example:
+
+```ts highlights={[["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "product.*",
+ ],
+})
+```
+
+`.*` means that all of data model's properties should be retrieved. You can also retrieve specific properties by replacing the `*` with the property name, for each property.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "product.id",
+ "product.title",
+ ],
+})
+```
+
+In the example above, you retrieve only the `id` and `title` properties of the `product` linked to a `post`.
+
+### Retrieve List Link Records
+
+If the linked data model has `isList` enabled in the link definition, pass in `fields` the data model's plural name suffixed with `.*`.
+
+For example:
+
+```ts highlights={[["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: [
+ "id",
+ "title",
+ "products.*",
+ ],
+})
+```
+
+In the example above, you retrieve all products linked to a post.
+
+### Apply Filters and Pagination on Linked Records
+
+Consider you want to apply filters or pagination configurations on the product(s) linked to `post`. To do that, you must query the module link's table instead.
+
+As mentioned in the [Module Link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.
+
+A module link's definition, exported by a file under `src/links`, has a special `entryPoint` property. Use this property when specifying the `entity` property in Query's `graph` method.
+
+For example:
+
+```ts highlights={queryLinkTableHighlights}
+import ProductPostLink from "../../../links/product-post"
+
+// ...
+
+const { data: productCustoms } = await query.graph({
+ entity: ProductPostLink.entryPoint,
+ fields: ["*", "product.*", "post.*"],
+ pagination: {
+ take: 5,
+ skip: 0,
+ },
+})
+```
+
+In the object passed to the `graph` method:
+
+- You pass the `entryPoint` property of the link definition as the value for `entity`. So, Query will retrieve records from the module link's table.
+- You pass three items to the `field` property:
+ - `*` to retrieve the link table's fields. This is useful if the link table has [custom columns](https://docs.medusajs.com/learn/fundamentals/module-links/custom-columns/index.html.md).
+ - `product.*` to retrieve the fields of a product record linked to a `Post` record.
+ - `post.*` to retrieve the fields of a `Post` record linked to a product record.
+
+You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
+
+The returned `data` is similar to the following:
+
+```json title="Example Result"
+[{
+ "id": "123",
+ "product_id": "prod_123",
+ "post_id": "123",
+ "product": {
+ "id": "prod_123",
+ // other product fields...
+ },
+ "post": {
+ "id": "123",
+ // other post fields...
+ }
+}]
+```
+
+***
+
+## Apply Filters
+
+```ts highlights={[["4"], ["5"], ["6"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: "post_123",
+ },
+})
+```
+
+The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
+
+In the example above, you filter the `post` records by the ID `post_123`.
+
+You can also filter by multiple values of a property. For example:
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"], ["9"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: [
+ "post_123",
+ "post_321",
+ ],
+ },
+})
+```
+
+In the example above, you filter the `post` records by multiple IDs.
+
+Filters don't apply on fields of linked data models from other modules. Refer to the [Retrieve Linked Records](#retrieve-linked-records) section for an alternative solution.
+
+### Advanced Query Filters
+
+Under the hood, Query uses the `listX` (`listPosts`) method of the data model's module's service to retrieve records. This method accepts a filter object that can be used to filter records.
+
+Those filters don't just allow you to filter by exact values. You can also filter by properties that don't match a value, match multiple values, and other filter types.
+
+Refer to the [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/tips/filtering/index.html.md) for examples of advanced filters. The following sections provide some quick examples.
+
+#### Filter by Not Matching a Value
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $ne: null,
+ },
+ },
+})
+```
+
+In the example above, only posts that have a title are retrieved.
+
+#### Filter by Not Matching Multiple Values
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $nin: ["My Post", "Another Post"],
+ },
+ },
+})
+```
+
+In the example above, only posts that don't have the title `My Post` or `Another Post` are retrieved.
+
+#### Filter by a Range
+
+```ts highlights={[["10"], ["11"], ["12"], ["13"], ["14"], ["15"]]}
+const startToday = new Date()
+startToday.setHours(0, 0, 0, 0)
+
+const endToday = new Date()
+endToday.setHours(23, 59, 59, 59)
+
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ published_at: {
+ $gt: startToday,
+ $lt: endToday,
+ },
+ },
+})
+```
+
+In the example above, only posts that were published today are retrieved.
+
+#### Filter Text by Like Value
+
+This filter only applies to text-like properties, including `text`, `id`, and `enum` properties.
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ title: {
+ $like: "%My%",
+ },
+ },
+})
+```
+
+In the example above, only posts that have the word `My` in their title are retrieved.
+
+#### Filter a Relation's Property
+
+```ts highlights={[["4"], ["5"], ["6"], ["7"], ["8"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ author: {
+ name: "John",
+ },
+ },
+})
+```
+
+While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module).
+
+In the example above, only posts that have an author with the name `John` are retrieved.
+
+***
+
+## Apply Pagination
+
+```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
+const {
+ data: posts,
+ metadata: { count, take, skip } = {},
+} = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ pagination: {
+ skip: 0,
+ take: 10,
+ },
+})
+```
+
+The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
+
+To paginate the returned records, pass the following properties to `pagination`:
+
+- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
+- `take`: The number of records to fetch.
+
+When you provide the pagination fields, the `query.graph` method's returned object has a `metadata` property. Its value is an object having the following properties:
+
+- skip: (\`number\`) The number of records skipped.
+- take: (\`number\`) The number of records requested to fetch.
+- count: (\`number\`) The total number of records.
+
+### Sort Records
+
+```ts highlights={[["5"], ["6"], ["7"]]}
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ pagination: {
+ order: {
+ name: "DESC",
+ },
+ },
+})
+```
+
+Sorting doesn't work on fields of linked data models from other modules.
+
+To sort returned records, pass an `order` property to `pagination`.
+
+The `order` property is an object whose keys are property names, and values are either:
+
+- `ASC` to sort records by that property in ascending order.
+- `DESC` to sort records by that property in descending order.
+
+***
+
+## Configure Query to Throw Errors
+
+By default, if Query doesn't find records matching your query, it returns an empty array. You can add option to configure Query to throw an error when no records are found.
+
+The `query.graph` method accepts as a second parameter an object that can have a `throwIfKeyNotFound` property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's `false`.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title"],
+ filters: {
+ id: "post_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, if no post is found with the ID `post_123`, Query will throw an error. This is useful to stop execution when a record is expected to exist.
+
+### Throw Error on Related Data Model
+
+The `throwIfKeyNotFound` option can also be used to throw an error if the ID of a related data model's record (in the same module) is passed in the filters, and the related record doesn't exist.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "post",
+ fields: ["id", "title", "author.*"],
+ filters: {
+ id: "post_123",
+ author_id: "author_123",
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error either if no post is found with the ID `post_123` or if its found but its author ID isn't `author_123`.
+
+In the above example, it's assumed that a post belongs to an author, so it has an `author_id` property. However, this also works in the opposite case, where an author has many posts.
+
+For example:
+
+```ts
+const { data: posts } = await query.graph({
+ entity: "author",
+ fields: ["id", "name", "posts.*"],
+ filters: {
+ id: "author_123",
+ posts: {
+ id: "post_123",
+ },
+ },
+}, {
+ throwIfKeyNotFound: true,
+})
+```
+
+In the example above, Query throws an error if no author is found with the ID `author_123` or if the author is found but doesn't have a post with the ID `post_123`.
+
+***
+
+## Request Query Configurations
+
+For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
+
+- Validates accepted query parameters, as explained in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md).
+- Parses configurations that are received as query parameters to be passed to Query.
+
+Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
+
+### Step 1: Add Middleware
+
+The first step is to use the `validateAndTransformQuery` middleware on the `GET` route. You add the middleware in `src/api/middlewares.ts`:
+
+```ts title="src/api/middlewares.ts"
+import {
+ validateAndTransformQuery,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
+import { createFindParams } from "@medusajs/medusa/api/utils/validators"
+
+export const GetCustomSchema = createFindParams()
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/customs",
+ method: "GET",
+ middlewares: [
+ validateAndTransformQuery(
+ GetCustomSchema,
+ {
+ defaults: [
+ "id",
+ "title",
+ "products.*",
+ ],
+ isList: true,
+ }
+ ),
+ ],
+ },
+ ],
+})
+```
+
+The `validateAndTransformQuery` accepts two parameters:
+
+1. A Zod validation schema for the query parameters, which you can learn more about in the [API Route Validation documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation/index.html.md). Medusa has a `createFindParams` utility that generates a Zod schema that accepts four query parameters:
+ 1. `fields`: The fields and relations to retrieve in the returned resources.
+ 2. `offset`: The number of items to skip before retrieving the returned items.
+ 3. `limit`: The maximum number of items to return.
+ 4. `order`: The fields to order the returned items by in ascending or descending order.
+2. A Query configuration object. It accepts the following properties:
+ 1. `defaults`: An array of default fields and relations to retrieve in each resource.
+ 2. `isList`: A boolean indicating whether a list of items are returned in the response.
+ 3. `allowed`: An array of fields and relations allowed to be passed in the `fields` query parameter.
+ 4. `defaultLimit`: A number indicating the default limit to use if no limit is provided. By default, it's `50`.
+
+### Step 2: Use Configurations in API Route
+
+After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
+
+The middleware transforms these parameters to configurations that you can pass to Query in your API route handler. These configurations are stored in the `queryConfig` parameter of the `MedusaRequest` object.
+
+As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), `remoteQueryConfig` has been deprecated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
+
+For example, Create the file `src/api/customs/route.ts` with the following content:
+
+```ts title="src/api/customs/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: posts } = await query.graph({
+ entity: "post",
+ ...req.queryConfig,
+ })
+
+ res.json({ posts: posts })
+}
+```
+
+This adds a `GET` API route at `/customs`, which is the API route you added the middleware for.
+
+In the API route, you pass `req.queryConfig` to `query.graph`. `queryConfig` has properties like `fields` and `pagination` to configure the query based on the default values you specified in the middleware, and the query parameters passed in the request.
+
+### Test it Out
+
+To test it out, start your Medusa application and send a `GET` request to the `/customs` API route. A list of records are retrieved with the specified fields in the middleware.
+
+```json title="Returned Data"
+{
+ "posts": [
+ {
+ "id": "123",
+ "title": "test"
+ }
+ ]
+}
+```
+
+Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
+
+Learn more about [specifing fields and relations](https://docs.medusajs.com/api/store#select-fields-and-relations) and [pagination](https://docs.medusajs.com/api/store#pagination) in the API reference.
+
+
# Scheduled Jobs Number of Executions
In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed.
@@ -12544,37 +12561,6 @@ So, it'll only execute 3 times, each every minute, then it won't be executed any
If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
-# Architectural Modules
-
-In this chapter, you’ll learn about architectural modules.
-
-## What is an Architectural Module?
-
-An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
-
-Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
-
-***
-
-## Architectural Module Types
-
-There are different architectural module types including:
-
-
-
-- Cache Module: Defines the caching mechanism or logic to cache computational results.
-- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
-- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
-- File Module: Integrates a storage service to handle uploading and managing files.
-- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
-
-***
-
-## Architectural Modules List
-
-Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
-
-
# Commerce Modules
In this chapter, you'll learn about Medusa's commerce modules.
@@ -12619,70 +12605,35 @@ export const countProductsStep = createStep(
Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features.
-# Module Container
+# Architectural Modules
-In this chapter, you'll learn about the module's container and how to resolve resources in that container.
+In this chapter, you’ll learn about architectural modules.
-Since modules are isolated, each module has a local container only used by the resources of that module.
+## What is an Architectural Module?
-So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
+An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
-### List of Registered Resources
-
-Find a list of resources or dependencies registered in a module's container in [the Container Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md).
+Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
***
-## Resolve Resources
+## Architectural Module Types
-### Services
+There are different architectural module types including:
-A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
+
-For example:
+- Cache Module: Defines the caching mechanism or logic to cache computational results.
+- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
+- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
+- File Module: Integrates a storage service to handle uploading and managing files.
+- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
-```ts highlights={[["4"], ["10"]]}
-import { Logger } from "@medusajs/framework/types"
+***
-type InjectedDependencies = {
- logger: Logger
-}
+## Architectural Modules List
-export default class BlogModuleService {
- protected logger_: Logger
-
- constructor({ logger }: InjectedDependencies) {
- this.logger_ = logger
-
- this.logger_.info("[BlogModuleService]: Hello World!")
- }
-
- // ...
-}
-```
-
-### Loader
-
-A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
-
-For example:
-
-```ts highlights={[["9"]]}
-import {
- LoaderOptions,
-} from "@medusajs/framework/types"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export default async function helloWorldLoader({
- container,
-}: LoaderOptions) {
- const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-
- logger.info("[helloWorldLoader]: Hello, World!")
-}
-```
+Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
# Perform Database Operations in a Service
@@ -13145,234 +13096,71 @@ class BlogModuleService {
```
-# Module Isolation
+# Module Container
-In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
+In this chapter, you'll learn about the module's container and how to resolve resources in that container.
-- Modules can't access resources, such as services or data models, from other modules.
-- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
+Since modules are isolated, each module has a local container only used by the resources of that module.
-## How are Modules Isolated?
+So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
-A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
+### List of Registered Resources
-For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
+Find a list of resources or dependencies registered in a module's container in [the Container Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md).
***
-## Why are Modules Isolated
+## Resolve Resources
-Some of the module isolation's benefits include:
+### Services
-- Integrate your module into any Medusa application without side-effects to your setup.
-- Replace existing modules with your custom implementation, if your use case is drastically different.
-- Use modules in other environments, such as Edge functions and Next.js apps.
-
-***
-
-## How to Extend Data Model of Another Module?
-
-To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-***
-
-## How to Use Services of Other Modules?
-
-If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
-
-Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
-
-### Example
-
-For example, consider you have two modules:
-
-1. A module that stores and manages brands in your application.
-2. A module that integrates a third-party Content Management System (CMS).
-
-To sync brands from your application to the third-party system, create the following steps:
-
-```ts title="Example Steps" highlights={stepsHighlights}
-const retrieveBrandsStep = createStep(
- "retrieve-brands",
- async (_, { container }) => {
- const brandModuleService = container.resolve(
- "brandModuleService"
- )
-
- const brands = await brandModuleService.listBrands()
-
- return new StepResponse(brands)
- }
-)
-
-const createBrandsInCmsStep = createStep(
- "create-brands-in-cms",
- async ({ brands }, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- const cmsBrands = await cmsModuleService.createBrands(brands)
-
- return new StepResponse(cmsBrands, cmsBrands)
- },
- async (brands, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- await cmsModuleService.deleteBrands(
- brands.map((brand) => brand.id)
- )
- }
-)
-```
-
-The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
-
-Then, create the following workflow that uses these steps:
-
-```ts title="Example Workflow"
-export const syncBrandsWorkflow = createWorkflow(
- "sync-brands",
- () => {
- const brands = retrieveBrandsStep()
-
- createBrandsInCmsStep({ brands })
- }
-)
-```
-
-You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
-
-
-# Multiple Services in a Module
-
-In this chapter, you'll learn how to use multiple services in a module.
-
-## Module's Main and Internal Services
-
-A module has one main service only, which is the service exported in the module's definition.
-
-However, you may use other services in your module to better organize your code or split functionalities. These are called internal services that can be resolved within your module, but not in external resources.
-
-***
-
-## How to Add an Internal Service
-
-### 1. Create Service
-
-To add an internal service, create it in the `services` directory of your module.
-
-For example, create the file `src/modules/blog/services/client.ts` with the following content:
-
-```ts title="src/modules/blog/services/client.ts"
-export class ClientService {
- async getMessage(): Promise {
- return "Hello, World!"
- }
-}
-```
-
-### 2. Export Service in Index
-
-Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
-
-For example, create the file `src/modules/blog/services/index.ts` with the following content:
-
-```ts title="src/modules/blog/services/index.ts"
-export * from "./client"
-```
-
-This exports the `ClientService`.
-
-### 3. Resolve Internal Service
-
-Internal services exported in the `services/index.ts` file of your module are now registered in the container and can be resolved in other services in the module as well as loaders.
-
-For example, in your main service:
-
-```ts title="src/modules/blog/service.ts" highlights={[["5"], ["13"]]}
-// other imports...
-import { ClientService } from "./services"
-
-type InjectedDependencies = {
- clientService: ClientService
-}
-
-class BlogModuleService extends MedusaService({
- Post,
-}){
- protected clientService_: ClientService
-
- constructor({ clientService }: InjectedDependencies) {
- super(...arguments)
- this.clientService_ = clientService
- }
-}
-```
-
-You can now use your internal service in your main service.
-
-***
-
-## Resolve Resources in Internal Service
-
-Resolve dependencies from your module's container in the constructor of your internal service.
+A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
For example:
-```ts
+```ts highlights={[["4"], ["10"]]}
import { Logger } from "@medusajs/framework/types"
type InjectedDependencies = {
logger: Logger
}
-export class ClientService {
+export default class BlogModuleService {
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
+
+ this.logger_.info("[BlogModuleService]: Hello World!")
}
+
+ // ...
}
```
-***
+### Loader
-## Access Module Options
-
-Your internal service can't access the module's options.
-
-To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`.
+A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
For example:
-```ts
-import { ConfigModule } from "@medusajs/framework/types"
-import { BLOG_MODULE } from ".."
+```ts highlights={[["9"]]}
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
-export type InjectedDependencies = {
- configModule: ConfigModule
-}
+export default async function helloWorldLoader({
+ container,
+}: LoaderOptions) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-export class ClientService {
- protected options: Record
-
- constructor({ configModule }: InjectedDependencies) {
- const moduleDef = configModule.modules[BLOG_MODULE]
-
- if (typeof moduleDef !== "boolean") {
- this.options = moduleDef.options
- }
- }
+ logger.info("[helloWorldLoader]: Hello, World!")
}
```
-The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key.
-
-If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
-
# Loaders
@@ -13644,6 +13432,107 @@ The following directories are optional and their content are explained more in t
- `loaders`: Holds the scripts to run on the Medusa application's start-up.
+# Module Isolation
+
+In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
+
+- Modules can't access resources, such as services or data models, from other modules.
+- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
+
+## How are Modules Isolated?
+
+A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
+
+For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
+
+***
+
+## Why are Modules Isolated
+
+Some of the module isolation's benefits include:
+
+- Integrate your module into any Medusa application without side-effects to your setup.
+- Replace existing modules with your custom implementation, if your use case is drastically different.
+- Use modules in other environments, such as Edge functions and Next.js apps.
+
+***
+
+## How to Extend Data Model of Another Module?
+
+To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+***
+
+## How to Use Services of Other Modules?
+
+If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
+
+Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
+
+### Example
+
+For example, consider you have two modules:
+
+1. A module that stores and manages brands in your application.
+2. A module that integrates a third-party Content Management System (CMS).
+
+To sync brands from your application to the third-party system, create the following steps:
+
+```ts title="Example Steps" highlights={stepsHighlights}
+const retrieveBrandsStep = createStep(
+ "retrieve-brands",
+ async (_, { container }) => {
+ const brandModuleService = container.resolve(
+ "brandModuleService"
+ )
+
+ const brands = await brandModuleService.listBrands()
+
+ return new StepResponse(brands)
+ }
+)
+
+const createBrandsInCmsStep = createStep(
+ "create-brands-in-cms",
+ async ({ brands }, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ const cmsBrands = await cmsModuleService.createBrands(brands)
+
+ return new StepResponse(cmsBrands, cmsBrands)
+ },
+ async (brands, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ await cmsModuleService.deleteBrands(
+ brands.map((brand) => brand.id)
+ )
+ }
+)
+```
+
+The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
+
+Then, create the following workflow that uses these steps:
+
+```ts title="Example Workflow"
+export const syncBrandsWorkflow = createWorkflow(
+ "sync-brands",
+ () => {
+ const brands = retrieveBrandsStep()
+
+ createBrandsInCmsStep({ brands })
+ }
+)
+```
+
+You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
+
+
# Module Options
In this chapter, you’ll learn about passing options to your module from the Medusa application’s configurations and using them in the module’s resources.
@@ -13847,51 +13736,6 @@ export default BlogModuleService
```
-# Access Workflow Errors
-
-In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
-
-## How to Access Workflow Errors?
-
-By default, when an error occurs in a workflow, it throws that error, and the execution stops.
-
-You can configure the workflow to return the errors instead so that you can access and handle them differently.
-
-For example:
-
-```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import myWorkflow from "../../../workflows/hello-world"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result, errors } = await myWorkflow(req.scope)
- .run({
- // ...
- throwOnError: false,
- })
-
- if (errors.length) {
- return res.send({
- errors: errors.map((error) => error.error),
- })
- }
-
- res.send(result)
-}
-
-```
-
-The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output.
-
-The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error.
-
-
# Service Factory
In this chapter, you’ll learn about what the service factory is and how to use it.
@@ -14067,232 +13911,431 @@ export default BlogModuleService
```
-# Expose a Workflow Hook
+# Multiple Services in a Module
-In this chapter, you'll learn how to expose a hook in your workflow.
+In this chapter, you'll learn how to use multiple services in a module.
-## When to Expose a Hook
+## Module's Main and Internal Services
-Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
+A module has one main service only, which is the service exported in the module's definition.
-Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
+However, you may use other services in your module to better organize your code or split functionalities. These are called internal services that can be resolved within your module, but not in external resources.
***
-## How to Expose a Hook in a Workflow?
+## How to Add an Internal Service
-To expose a hook in your workflow, use `createHook` from the Workflows SDK.
+### 1. Create Service
+
+To add an internal service, create it in the `services` directory of your module.
+
+For example, create the file `src/modules/blog/services/client.ts` with the following content:
+
+```ts title="src/modules/blog/services/client.ts"
+export class ClientService {
+ async getMessage(): Promise {
+ return "Hello, World!"
+ }
+}
+```
+
+### 2. Export Service in Index
+
+Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
+
+For example, create the file `src/modules/blog/services/index.ts` with the following content:
+
+```ts title="src/modules/blog/services/index.ts"
+export * from "./client"
+```
+
+This exports the `ClientService`.
+
+### 3. Resolve Internal Service
+
+Internal services exported in the `services/index.ts` file of your module are now registered in the container and can be resolved in other services in the module as well as loaders.
+
+For example, in your main service:
+
+```ts title="src/modules/blog/service.ts" highlights={[["5"], ["13"]]}
+// other imports...
+import { ClientService } from "./services"
+
+type InjectedDependencies = {
+ clientService: ClientService
+}
+
+class BlogModuleService extends MedusaService({
+ Post,
+}){
+ protected clientService_: ClientService
+
+ constructor({ clientService }: InjectedDependencies) {
+ super(...arguments)
+ this.clientService_ = clientService
+ }
+}
+```
+
+You can now use your internal service in your main service.
+
+***
+
+## Resolve Resources in Internal Service
+
+Resolve dependencies from your module's container in the constructor of your internal service.
For example:
-```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
-import {
- createStep,
- createHook,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { createProductStep } from "./steps/create-product"
-
-export const myWorkflow = createWorkflow(
- "my-workflow",
- function (input) {
- const product = createProductStep(input)
- const productCreatedHook = createHook(
- "productCreated",
- { productId: product.id }
- )
-
- return new WorkflowResponse(product, {
- hooks: [productCreatedHook],
- })
- }
-)
-```
-
-The `createHook` function accepts two parameters:
-
-1. The first is a string indicating the hook's name. You use this to consume the hook later.
-2. The second is the input to pass to the hook handler.
-
-The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks.
-
-### How to Consume the Hook?
-
-To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
-
-```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
-import { myWorkflow } from "../my-workflow"
-
-myWorkflow.hooks.productCreated(
- async ({ productId }, { container }) => {
- // TODO perform an action
- }
-)
-```
-
-The hook is available on the workflow's `hooks` property using its name `productCreated`.
-
-You invoke the hook, passing a step function (the hook handler) as a parameter.
-
-
-# Conditions in Workflows with When-Then
-
-In this chapter, you'll learn how to execute an action based on a condition in a workflow using when-then from the Workflows SDK.
-
-## Why If-Conditions Aren't Allowed in Workflows?
-
-Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. At that point, variables in the workflow don't have any values. They only do when you execute the workflow.
-
-So, you can't use an if-condition that checks a variable's value, as the condition will be evaluated when Medusa creates the internal representation of the workflow, rather than during execution.
-
-Instead, use when-then from the Workflows SDK. It allows you to perform steps in a workflow only if a condition that you specify is satisfied.
-
-Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
-
-***
-
-## How to use When-Then?
-
-The Workflows SDK provides a `when` function that is used to check whether a condition is true. You chain a `then` function to `when` that specifies the steps to execute if the condition in `when` is satisfied.
-
-For example:
-
-```ts highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- when,
-} from "@medusajs/framework/workflows-sdk"
-// step imports...
-
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
-
- const result = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- const stepResult = isActiveStep()
- return stepResult
- })
-
- // executed without condition
- const anotherStepResult = anotherStep(result)
-
- return new WorkflowResponse(
- anotherStepResult
- )
- }
-)
-```
-
-In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
-
-### When Parameters
-
-`when` accepts the following parameters:
-
-1. The first parameter is either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
-2. The second parameter is a function that returns a boolean indicating whether to execute the action in `then`.
-
-### Then Parameters
-
-To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
-
-The callback function is only executed if `when`'s second parameter function returns a `true` value.
-
-***
-
-## Implementing If-Else with When-Then
-
-when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
-
-For example:
-
-```ts highlights={ifElseHighlights}
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
-
- const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- return isActiveStep()
- })
-
- const notIsActiveResult = when(
- input,
- (input) => {
- return !input.is_active
- }
- ).then(() => {
- return notIsActiveStep()
- })
-
- // ...
- }
-)
-```
-
-In the above workflow, you use two `when-then` blocks. The first one performs a step if `input.is_active` is `true`, and the second performs a step if `input.is_active` is `false`, acting as an else condition.
-
-***
-
-## Specify Name for When-Then
-
-Internally, `when-then` blocks have a unique name similar to a step. When you return a step's result in a `when-then` block, the block's name is derived from the step's name. For example:
-
```ts
-const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
+import { Logger } from "@medusajs/framework/types"
+
+type InjectedDependencies = {
+ logger: Logger
+}
+
+export class ClientService {
+ protected logger_: Logger
+
+ constructor({ logger }: InjectedDependencies) {
+ this.logger_ = logger
}
-).then(() => {
- return isActiveStep()
-})
+}
```
-This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
+***
-However, if you need to return in your `when-then` block something other than a step's result, you need to specify a unique step name for that block. Otherwise, Medusa will generate a random name for it which can cause unexpected errors in production.
+## Access Module Options
-You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
+Your internal service can't access the module's options.
-```ts highlights={nameHighlights}
-const { isActive } = when(
- "check-is-active",
- input,
- (input) => {
- return input.is_active
+To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`.
+
+For example:
+
+```ts
+import { ConfigModule } from "@medusajs/framework/types"
+import { BLOG_MODULE } from ".."
+
+export type InjectedDependencies = {
+ configModule: ConfigModule
+}
+
+export class ClientService {
+ protected options: Record
+
+ constructor({ configModule }: InjectedDependencies) {
+ const moduleDef = configModule.modules[BLOG_MODULE]
+
+ if (typeof moduleDef !== "boolean") {
+ this.options = moduleDef.options
+ }
}
-).then(() => {
- const isActive = isActiveStep()
-
- return {
- isActive,
- }
-})
+}
```
-Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
+The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key.
-1. A unique name to be assigned to the `when-then` block.
-2. Either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
-3. A function that returns a boolean indicating whether to execute the action in `then`.
+If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
-The second and third parameters are the same as the parameters you previously passed to `when`.
+
+# Access Workflow Errors
+
+In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
+
+## How to Access Workflow Errors?
+
+By default, when an error occurs in a workflow, it throws that error, and the execution stops.
+
+You can configure the workflow to return the errors instead so that you can access and handle them differently.
+
+For example:
+
+```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import myWorkflow from "../../../workflows/hello-world"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result, errors } = await myWorkflow(req.scope)
+ .run({
+ // ...
+ throwOnError: false,
+ })
+
+ if (errors.length) {
+ return res.send({
+ errors: errors.map((error) => error.error),
+ })
+ }
+
+ res.send(result)
+}
+
+```
+
+The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output.
+
+The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error.
+
+
+# Compensation Function
+
+In this chapter, you'll learn what a compensation function is and how to add it to a step.
+
+## What is a Compensation Function
+
+A compensation function rolls back or undoes changes made by a step when an error occurs in the workflow.
+
+For example, if a step creates a record, the compensation function deletes the record when an error occurs later in the workflow.
+
+By using compensation functions, you provide a mechanism that guarantees data consistency in your application and across systems.
+
+***
+
+## How to add a Compensation Function?
+
+A compensation function is passed as a second parameter to the `createStep` function.
+
+For example, create the file `src/workflows/hello-world.ts` with the following content:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["15"], ["16"], ["17"]]} collapsibleLines="1-5" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ "step-1",
+ async () => {
+ const message = `Hello from step one!`
+
+ console.log(message)
+
+ return new StepResponse(message)
+ },
+ async () => {
+ console.log("Oops! Rolling back my changes...")
+ }
+)
+```
+
+Each step can have a compensation function. The compensation function only runs if an error occurs throughout the workflow.
+
+***
+
+## Test the Compensation Function
+
+Create a step in the same `src/workflows/hello-world.ts` file that throws an error:
+
+```ts title="src/workflows/hello-world.ts"
+const step2 = createStep(
+ "step-2",
+ async () => {
+ throw new Error("Throwing an error...")
+ }
+)
+```
+
+Then, create a workflow that uses the steps:
+
+```ts title="src/workflows/hello-world.ts" collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+// other imports...
+
+// steps...
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ const str1 = step1()
+ step2()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+export default myWorkflow
+```
+
+Finally, execute the workflow from an API route:
+
+```ts title="src/api/workflow/route.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import myWorkflow from "../../../workflows/hello-world"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await myWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+Run the Medusa application and send a `GET` request to `/workflow`:
+
+```bash
+curl http://localhost:9000/workflow
+```
+
+In the console, you'll see:
+
+- `Hello from step one!` logged in the terminal, indicating that the first step ran successfully.
+- `Oops! Rolling back my changes...` logged in the terminal, indicating that the second step failed and the compensation function of the first step ran consequently.
+
+***
+
+## Pass Input to Compensation Function
+
+If a step creates a record, the compensation function must receive the ID of the record to remove it.
+
+To pass input to the compensation function, pass a second parameter in the `StepResponse` returned by the step.
+
+For example:
+
+```ts highlights={inputHighlights}
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ "step-1",
+ async () => {
+ return new StepResponse(
+ `Hello from step one!`,
+ { message: "Oops! Rolling back my changes..." }
+ )
+ },
+ async ({ message }) => {
+ console.log(message)
+ }
+)
+```
+
+In this example, the step passes an object as a second parameter to `StepResponse`.
+
+The compensation function receives the object and uses its `message` property to log a message.
+
+***
+
+## Resolve Resources from the Medusa Container
+
+The compensation function receives an object second parameter. The object has a `container` property that you use to resolve resources from the Medusa container.
+
+For example:
+
+```ts
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+const step1 = createStep(
+ "step-1",
+ async () => {
+ return new StepResponse(
+ `Hello from step one!`,
+ { message: "Oops! Rolling back my changes..." }
+ )
+ },
+ async ({ message }, { container }) => {
+ const logger = container.resolve(
+ ContainerRegistrationKeys.LOGGER
+ )
+
+ logger.info(message)
+ }
+)
+```
+
+In this example, you use the `container` property in the second object parameter of the compensation function to resolve the logger.
+
+You then use the logger to log a message.
+
+***
+
+## Handle Errors in Loops
+
+This feature is only available after [Medusa v2.0.5](https://github.com/medusajs/medusa/releases/tag/v2.0.5).
+
+Consider you have a module that integrates a third-party ERP system, and you're creating a workflow that deletes items in that ERP. You may have the following step:
+
+```ts
+// other imports...
+import { promiseAll } from "@medusajs/framework/utils"
+
+type StepInput = {
+ ids: string[]
+}
+
+const step1 = createStep(
+ "step-1",
+ async ({ ids }: StepInput, { container }) => {
+ const erpModuleService = container.resolve(
+ ERP_MODULE
+ )
+ const prevData: unknown[] = []
+
+ await promiseAll(
+ ids.map(async (id) => {
+ const data = await erpModuleService.retrieve(id)
+
+ await erpModuleService.delete(id)
+
+ prevData.push(id)
+ })
+ )
+
+ return new StepResponse(ids, prevData)
+ }
+)
+```
+
+In the step, you loop over the IDs to retrieve the item's data, store them in a `prevData` variable, then delete them using the ERP Module's service. You then pass the `prevData` variable to the compensation function.
+
+However, if an error occurs in the loop, the `prevData` variable won't be passed to the compensation function as the execution never reached the return statement.
+
+To handle errors in the loop so that the compensation function receives the last version of `prevData` before the error occurred, you wrap the loop in a try-catch block. Then, in the catch block, you invoke and return the `StepResponse.permanentFailure` function:
+
+```ts highlights={highlights}
+try {
+ await promiseAll(
+ ids.map(async (id) => {
+ const data = await erpModuleService.retrieve(id)
+
+ await erpModuleService.delete(id)
+
+ prevData.push(id)
+ })
+ )
+} catch (e) {
+ return StepResponse.permanentFailure(
+ `An error occurred: ${e}`,
+ prevData
+ )
+}
+```
+
+The `StepResponse.permanentFailure` fails the step and its workflow, triggering current and previous steps' compensation functions. The `permanentFailure` function accepts as a first parameter the error message, which is saved in the workflow's error details, and as a second parameter the data to pass to the compensation function.
+
+So, if an error occurs during the loop, the compensation function will still receive the `prevData` variable to undo the changes made before the step failed.
# Workflow Constraints
@@ -14643,258 +14686,73 @@ const step1 = createStep(
```
-# Compensation Function
+# Expose a Workflow Hook
-In this chapter, you'll learn what a compensation function is and how to add it to a step.
+In this chapter, you'll learn how to expose a hook in your workflow.
-## What is a Compensation Function
+## When to Expose a Hook
-A compensation function rolls back or undoes changes made by a step when an error occurs in the workflow.
+Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
-For example, if a step creates a record, the compensation function deletes the record when an error occurs later in the workflow.
-
-By using compensation functions, you provide a mechanism that guarantees data consistency in your application and across systems.
+Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
***
-## How to add a Compensation Function?
+## How to Expose a Hook in a Workflow?
-A compensation function is passed as a second parameter to the `createStep` function.
+To expose a hook in your workflow, use `createHook` from the Workflows SDK.
-For example, create the file `src/workflows/hello-world.ts` with the following content:
+For example:
-```ts title="src/workflows/hello-world.ts" highlights={[["15"], ["16"], ["17"]]} collapsibleLines="1-5" expandButtonLabel="Show Imports"
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- const message = `Hello from step one!`
-
- console.log(message)
-
- return new StepResponse(message)
- },
- async () => {
- console.log("Oops! Rolling back my changes...")
- }
-)
-```
-
-Each step can have a compensation function. The compensation function only runs if an error occurs throughout the workflow.
-
-***
-
-## Test the Compensation Function
-
-Create a step in the same `src/workflows/hello-world.ts` file that throws an error:
-
-```ts title="src/workflows/hello-world.ts"
-const step2 = createStep(
- "step-2",
- async () => {
- throw new Error("Throwing an error...")
- }
-)
-```
-
-Then, create a workflow that uses the steps:
-
-```ts title="src/workflows/hello-world.ts" collapsibleLines="1-8" expandButtonLabel="Show Imports"
+```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
import {
+ createStep,
+ createHook,
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
-// other imports...
+import { createProductStep } from "./steps/create-product"
-// steps...
-
-const myWorkflow = createWorkflow(
- "hello-world",
+export const myWorkflow = createWorkflow(
+ "my-workflow",
function (input) {
- const str1 = step1()
- step2()
-
- return new WorkflowResponse({
- message: str1,
- })
-})
-
-export default myWorkflow
-```
-
-Finally, execute the workflow from an API route:
-
-```ts title="src/api/workflow/route.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import myWorkflow from "../../../workflows/hello-world"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await myWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-Run the Medusa application and send a `GET` request to `/workflow`:
-
-```bash
-curl http://localhost:9000/workflow
-```
-
-In the console, you'll see:
-
-- `Hello from step one!` logged in the terminal, indicating that the first step ran successfully.
-- `Oops! Rolling back my changes...` logged in the terminal, indicating that the second step failed and the compensation function of the first step ran consequently.
-
-***
-
-## Pass Input to Compensation Function
-
-If a step creates a record, the compensation function must receive the ID of the record to remove it.
-
-To pass input to the compensation function, pass a second parameter in the `StepResponse` returned by the step.
-
-For example:
-
-```ts highlights={inputHighlights}
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- return new StepResponse(
- `Hello from step one!`,
- { message: "Oops! Rolling back my changes..." }
- )
- },
- async ({ message }) => {
- console.log(message)
- }
-)
-```
-
-In this example, the step passes an object as a second parameter to `StepResponse`.
-
-The compensation function receives the object and uses its `message` property to log a message.
-
-***
-
-## Resolve Resources from the Medusa Container
-
-The compensation function receives an object second parameter. The object has a `container` property that you use to resolve resources from the Medusa container.
-
-For example:
-
-```ts
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-const step1 = createStep(
- "step-1",
- async () => {
- return new StepResponse(
- `Hello from step one!`,
- { message: "Oops! Rolling back my changes..." }
- )
- },
- async ({ message }, { container }) => {
- const logger = container.resolve(
- ContainerRegistrationKeys.LOGGER
+ const product = createProductStep(input)
+ const productCreatedHook = createHook(
+ "productCreated",
+ { productId: product.id }
)
- logger.info(message)
- }
-)
-```
-
-In this example, you use the `container` property in the second object parameter of the compensation function to resolve the logger.
-
-You then use the logger to log a message.
-
-***
-
-## Handle Errors in Loops
-
-This feature is only available after [Medusa v2.0.5](https://github.com/medusajs/medusa/releases/tag/v2.0.5).
-
-Consider you have a module that integrates a third-party ERP system, and you're creating a workflow that deletes items in that ERP. You may have the following step:
-
-```ts
-// other imports...
-import { promiseAll } from "@medusajs/framework/utils"
-
-type StepInput = {
- ids: string[]
-}
-
-const step1 = createStep(
- "step-1",
- async ({ ids }: StepInput, { container }) => {
- const erpModuleService = container.resolve(
- ERP_MODULE
- )
- const prevData: unknown[] = []
-
- await promiseAll(
- ids.map(async (id) => {
- const data = await erpModuleService.retrieve(id)
-
- await erpModuleService.delete(id)
-
- prevData.push(id)
- })
- )
-
- return new StepResponse(ids, prevData)
- }
-)
-```
-
-In the step, you loop over the IDs to retrieve the item's data, store them in a `prevData` variable, then delete them using the ERP Module's service. You then pass the `prevData` variable to the compensation function.
-
-However, if an error occurs in the loop, the `prevData` variable won't be passed to the compensation function as the execution never reached the return statement.
-
-To handle errors in the loop so that the compensation function receives the last version of `prevData` before the error occurred, you wrap the loop in a try-catch block. Then, in the catch block, you invoke and return the `StepResponse.permanentFailure` function:
-
-```ts highlights={highlights}
-try {
- await promiseAll(
- ids.map(async (id) => {
- const data = await erpModuleService.retrieve(id)
-
- await erpModuleService.delete(id)
-
- prevData.push(id)
+ return new WorkflowResponse(product, {
+ hooks: [productCreatedHook],
})
- )
-} catch (e) {
- return StepResponse.permanentFailure(
- `An error occurred: ${e}`,
- prevData
- )
-}
+ }
+)
```
-The `StepResponse.permanentFailure` fails the step and its workflow, triggering current and previous steps' compensation functions. The `permanentFailure` function accepts as a first parameter the error message, which is saved in the workflow's error details, and as a second parameter the data to pass to the compensation function.
+The `createHook` function accepts two parameters:
-So, if an error occurs during the loop, the compensation function will still receive the `prevData` variable to undo the changes made before the step failed.
+1. The first is a string indicating the hook's name. You use this to consume the hook later.
+2. The second is the input to pass to the hook handler.
+
+The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks.
+
+### How to Consume the Hook?
+
+To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
+
+```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
+import { myWorkflow } from "../my-workflow"
+
+myWorkflow.hooks.productCreated(
+ async ({ productId }, { container }) => {
+ // TODO perform an action
+ }
+)
+```
+
+The hook is available on the workflow's `hooks` property using its name `productCreated`.
+
+You invoke the hook, passing a step function (the hook handler) as a parameter.
# Execute Another Workflow
@@ -15027,133 +14885,6 @@ const workflow = createWorkflow(
In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
-# Multiple Step Usage in Workflow
-
-In this chapter, you'll learn how to use a step multiple times in a workflow.
-
-## Problem Reusing a Step in a Workflow
-
-In some cases, you may need to use a step multiple times in the same workflow.
-
-The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products.
-
-Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step:
-
-```ts
-const useQueryGraphStep = createStep(
- "use-query-graph"
- // ...
-)
-```
-
-This causes an error when you use the same step multiple times in a workflow, as it's registered in the workflow as two steps having the same ID:
-
-```ts
-const helloWorkflow = createWorkflow(
- "hello",
- () => {
- const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: ["id"],
- })
-
- // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW
- const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: ["id"],
- })
- }
-)
-```
-
-The next section explains how to fix this issue to use the same step multiple times in a workflow.
-
-***
-
-## How to Use a Step Multiple Times in a Workflow?
-
-When you execute a step in a workflow, you can chain a `config` method to it to change the step's config.
-
-Use the `config` method to change a step's ID for a single execution.
-
-So, this is the correct way to write the example above:
-
-```ts highlights={highlights}
-const helloWorkflow = createWorkflow(
- "hello",
- () => {
- const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: ["id"],
- })
-
- // ✓ No error occurs, the step has a different ID.
- const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: ["id"],
- }).config({ name: "fetch-customers" })
- }
-)
-```
-
-The `config` method accepts an object with a `name` property. Its value is a new ID of the step to use for this execution only.
-
-The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`.
-
-
-# Run Workflow Steps in Parallel
-
-In this chapter, you’ll learn how to run workflow steps in parallel.
-
-## parallelize Utility Function
-
-If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK.
-
-The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
-
-For example:
-
-```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import {
- createWorkflow,
- WorkflowResponse,
- parallelize,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductStep,
- getProductStep,
- createPricesStep,
- attachProductToSalesChannelStep,
-} from "./steps"
-
-interface WorkflowInput {
- title: string
-}
-
-const myWorkflow = createWorkflow(
- "my-workflow",
- (input: WorkflowInput) => {
- const product = createProductStep(input)
-
- const [prices, productSalesChannel] = parallelize(
- createPricesStep(product),
- attachProductToSalesChannelStep(product)
- )
-
- const refetchedProduct = getProductStep(product.id)
-
- return new WorkflowResponse(refetchedProduct)
- }
-)
-```
-
-The `parallelize` function accepts the steps to run in parallel as a parameter.
-
-It returns an array of the steps' results in the same order they're passed to the `parallelize` function.
-
-So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
-
-
# Long-Running Workflows
In this chapter, you’ll learn what a long-running workflow is and how to configure it.
@@ -15449,6 +15180,256 @@ To find a full example of a long-running workflow, refer to the [restaurant-deli
In the recipe, you use a long-running workflow that moves an order from placed to completed. The workflow waits for the restaurant to accept the order, the driver to pick up the order, and other external actions.
+# Run Workflow Steps in Parallel
+
+In this chapter, you’ll learn how to run workflow steps in parallel.
+
+## parallelize Utility Function
+
+If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK.
+
+The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
+
+For example:
+
+```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+import {
+ createWorkflow,
+ WorkflowResponse,
+ parallelize,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductStep,
+ getProductStep,
+ createPricesStep,
+ attachProductToSalesChannelStep,
+} from "./steps"
+
+interface WorkflowInput {
+ title: string
+}
+
+const myWorkflow = createWorkflow(
+ "my-workflow",
+ (input: WorkflowInput) => {
+ const product = createProductStep(input)
+
+ const [prices, productSalesChannel] = parallelize(
+ createPricesStep(product),
+ attachProductToSalesChannelStep(product)
+ )
+
+ const refetchedProduct = getProductStep(product.id)
+
+ return new WorkflowResponse(refetchedProduct)
+ }
+)
+```
+
+The `parallelize` function accepts the steps to run in parallel as a parameter.
+
+It returns an array of the steps' results in the same order they're passed to the `parallelize` function.
+
+So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
+
+
+# Retry Failed Steps
+
+In this chapter, you’ll learn how to configure steps to allow retrial on failure.
+
+## What is a Step Retrial?
+
+A step retrial is a mechanism that allows a step to be retried automatically when it fails. This is useful for handling transient errors, such as network issues or temporary unavailability of a service.
+
+When a step fails, the workflow engine can automatically retry the step a specified number of times before marking the workflow as failed. This can help improve the reliability and resilience of your workflows.
+
+You can also configure the interval between retries, allowing you to wait for a certain period before attempting the step again. This is useful when the failure is due to a temporary issue that may resolve itself after some time.
+
+For example, if a step captures a payment, you may want to retry it the next day until the payment is successful or the maximum number of retries is reached.
+
+***
+
+## Configure a Step’s Retrial
+
+By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
+
+You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ },
+ async () => {
+ console.log("Executing step 1")
+
+ throw new Error("Oops! Something happened.")
+ }
+)
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+export default myWorkflow
+```
+
+The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
+
+When you execute the above workflow, you’ll see the following result in the terminal:
+
+```bash
+Executing step 1
+Executing step 1
+Executing step 1
+error: Oops! Something happened.
+Error: Oops! Something happened.
+```
+
+The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
+
+***
+
+## Step Retry Intervals
+
+By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ retryInterval: 2, // 2 seconds
+ },
+ async () => {
+ // ...
+ }
+)
+```
+
+In this example, if the step fails, it will be retried after two seconds.
+
+### Maximum Retry Interval
+
+The `retryInterval` property's maximum value is [Number.MAX\_SAFE\_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER). So, you can set a very long wait time before the step is retried, allowing you to retry steps after a long period.
+
+For example, to retry a step after a day:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ retryInterval: 86400, // 1 day
+ },
+ async () => {
+ // ...
+ }
+)
+```
+
+In this example, if the step fails, it will be retried after `86400` seconds (one day).
+
+### Interval Changes Workflow to Long-Running
+
+By setting `retryInterval` on a step, a workflow that uses that step becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. This is useful when creating workflows that may fail and should run for a long time until they succeed, such as waiting for a payment to be captured or a shipment to be delivered.
+
+However, since the long-running workflow runs in the background, you won't receive its result or errors immediately when you execute the workflow.
+
+Instead, you must subscribe to the workflow's execution using the Workflow Engine Module Service. Learn more about it in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result/index.html.md).
+
+
+# Multiple Step Usage in Workflow
+
+In this chapter, you'll learn how to use a step multiple times in a workflow.
+
+## Problem Reusing a Step in a Workflow
+
+In some cases, you may need to use a step multiple times in the same workflow.
+
+The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products.
+
+Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step:
+
+```ts
+const useQueryGraphStep = createStep(
+ "use-query-graph"
+ // ...
+)
+```
+
+This causes an error when you use the same step multiple times in a workflow, as it's registered in the workflow as two steps having the same ID:
+
+```ts
+const helloWorkflow = createWorkflow(
+ "hello",
+ () => {
+ const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id"],
+ })
+
+ // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW
+ const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: ["id"],
+ })
+ }
+)
+```
+
+The next section explains how to fix this issue to use the same step multiple times in a workflow.
+
+***
+
+## How to Use a Step Multiple Times in a Workflow?
+
+When you execute a step in a workflow, you can chain a `config` method to it to change the step's config.
+
+Use the `config` method to change a step's ID for a single execution.
+
+So, this is the correct way to write the example above:
+
+```ts highlights={highlights}
+const helloWorkflow = createWorkflow(
+ "hello",
+ () => {
+ const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id"],
+ })
+
+ // ✓ No error occurs, the step has a different ID.
+ const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: ["id"],
+ }).config({ name: "fetch-customers" })
+ }
+)
+```
+
+The `config` method accepts an object with a `name` property. Its value is a new ID of the step to use for this execution only.
+
+The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`.
+
+
# Store Workflow Executions
In this chapter, you'll learn how to store workflow executions in the database and access them later.
@@ -15594,129 +15575,6 @@ if (workflowExecution.state === "failed") {
Other state values include `done`, `invoking`, and `compensating`.
-# Retry Failed Steps
-
-In this chapter, you’ll learn how to configure steps to allow retrial on failure.
-
-## What is a Step Retrial?
-
-A step retrial is a mechanism that allows a step to be retried automatically when it fails. This is useful for handling transient errors, such as network issues or temporary unavailability of a service.
-
-When a step fails, the workflow engine can automatically retry the step a specified number of times before marking the workflow as failed. This can help improve the reliability and resilience of your workflows.
-
-You can also configure the interval between retries, allowing you to wait for a certain period before attempting the step again. This is useful when the failure is due to a temporary issue that may resolve itself after some time.
-
-For example, if a step captures a payment, you may want to retry it the next day until the payment is successful or the maximum number of retries is reached.
-
-***
-
-## Configure a Step’s Retrial
-
-By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
-
-You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- },
- async () => {
- console.log("Executing step 1")
-
- throw new Error("Oops! Something happened.")
- }
-)
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function () {
- const str1 = step1()
-
- return new WorkflowResponse({
- message: str1,
- })
-})
-
-export default myWorkflow
-```
-
-The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
-
-When you execute the above workflow, you’ll see the following result in the terminal:
-
-```bash
-Executing step 1
-Executing step 1
-Executing step 1
-error: Oops! Something happened.
-Error: Oops! Something happened.
-```
-
-The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
-
-***
-
-## Step Retry Intervals
-
-By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- retryInterval: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
-```
-
-In this example, if the step fails, it will be retried after two seconds.
-
-### Maximum Retry Interval
-
-The `retryInterval` property's maximum value is [Number.MAX\_SAFE\_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER). So, you can set a very long wait time before the step is retried, allowing you to retry steps after a long period.
-
-For example, to retry a step after a day:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- retryInterval: 86400, // 1 day
- },
- async () => {
- // ...
- }
-)
-```
-
-In this example, if the step fails, it will be retried after `86400` seconds (one day).
-
-### Interval Changes Workflow to Long-Running
-
-By setting `retryInterval` on a step, a workflow that uses that step becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. This is useful when creating workflows that may fail and should run for a long time until they succeed, such as waiting for a payment to be captured or a shipment to be delivered.
-
-However, since the long-running workflow runs in the background, you won't receive its result or errors immediately when you execute the workflow.
-
-Instead, you must subscribe to the workflow's execution using the Workflow Engine Module Service. Learn more about it in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result/index.html.md).
-
-
# Workflow Hooks
In this chapter, you'll learn what a workflow hook is and how to consume them.
@@ -16046,6 +15904,165 @@ const myWorkflow = createWorkflow(
```
+# Conditions in Workflows with When-Then
+
+In this chapter, you'll learn how to execute an action based on a condition in a workflow using when-then from the Workflows SDK.
+
+## Why If-Conditions Aren't Allowed in Workflows?
+
+Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. At that point, variables in the workflow don't have any values. They only do when you execute the workflow.
+
+So, you can't use an if-condition that checks a variable's value, as the condition will be evaluated when Medusa creates the internal representation of the workflow, rather than during execution.
+
+Instead, use when-then from the Workflows SDK. It allows you to perform steps in a workflow only if a condition that you specify is satisfied.
+
+Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
+
+***
+
+## How to use When-Then?
+
+The Workflows SDK provides a `when` function that is used to check whether a condition is true. You chain a `then` function to `when` that specifies the steps to execute if the condition in `when` is satisfied.
+
+For example:
+
+```ts highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ when,
+} from "@medusajs/framework/workflows-sdk"
+// step imports...
+
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
+
+ const result = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+ ).then(() => {
+ const stepResult = isActiveStep()
+ return stepResult
+ })
+
+ // executed without condition
+ const anotherStepResult = anotherStep(result)
+
+ return new WorkflowResponse(
+ anotherStepResult
+ )
+ }
+)
+```
+
+In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
+
+### When Parameters
+
+`when` accepts the following parameters:
+
+1. The first parameter is either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
+2. The second parameter is a function that returns a boolean indicating whether to execute the action in `then`.
+
+### Then Parameters
+
+To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
+
+The callback function is only executed if `when`'s second parameter function returns a `true` value.
+
+***
+
+## Implementing If-Else with When-Then
+
+when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
+
+For example:
+
+```ts highlights={ifElseHighlights}
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
+
+ const isActiveResult = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+ ).then(() => {
+ return isActiveStep()
+ })
+
+ const notIsActiveResult = when(
+ input,
+ (input) => {
+ return !input.is_active
+ }
+ ).then(() => {
+ return notIsActiveStep()
+ })
+
+ // ...
+ }
+)
+```
+
+In the above workflow, you use two `when-then` blocks. The first one performs a step if `input.is_active` is `true`, and the second performs a step if `input.is_active` is `false`, acting as an else condition.
+
+***
+
+## Specify Name for When-Then
+
+Internally, `when-then` blocks have a unique name similar to a step. When you return a step's result in a `when-then` block, the block's name is derived from the step's name. For example:
+
+```ts
+const isActiveResult = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+).then(() => {
+ return isActiveStep()
+})
+```
+
+This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
+
+However, if you need to return in your `when-then` block something other than a step's result, you need to specify a unique step name for that block. Otherwise, Medusa will generate a random name for it which can cause unexpected errors in production.
+
+You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
+
+```ts highlights={nameHighlights}
+const { isActive } = when(
+ "check-is-active",
+ input,
+ (input) => {
+ return input.is_active
+ }
+).then(() => {
+ const isActive = isActiveStep()
+
+ return {
+ isActive,
+ }
+})
+```
+
+Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
+
+1. A unique name to be assigned to the `when-then` block.
+2. Either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
+3. A function that returns a boolean indicating whether to execute the action in `then`.
+
+The second and third parameters are the same as the parameters you previously passed to `when`.
+
+
# Workflow Timeout
In this chapter, you’ll learn how to set a timeout for workflows and steps.
@@ -16226,6 +16243,270 @@ export const languages: Language[] = [
Our team will perform a general review on your PR and merge it if no issues are found. The translation will be available in the admin after the next release.
+# Docs Contribution Guidelines
+
+Thank you for your interest in contributing to the documentation! You will be helping the open source community and other developers interested in learning more about Medusa and using it.
+
+This guide is specific to contributing to the documentation. If you’re interested in contributing to Medusa’s codebase, check out the [contributing guidelines in the Medusa GitHub repository](https://github.com/medusajs/medusa/blob/develop/CONTRIBUTING.md).
+
+## What Can You Contribute?
+
+You can contribute to the Medusa documentation in the following ways:
+
+- Fixes to existing content. This includes small fixes like typos, or adding missing information.
+- Additions to the documentation. If you think a documentation page can be useful to other developers, you can contribute by adding it.
+ - Make sure to open an issue first in the [medusa repository](https://github.com/medusajs/medusa) to confirm that you can add that documentation page.
+- Fixes to UI components and tooling. If you find a bug while browsing the documentation, you can contribute by fixing it.
+
+***
+
+## Documentation Workspace
+
+Medusa's documentation projects are all part of the documentation yarn workspace, which you can find in the [medusa repository](https://github.com/medusajs/medusa) under the `www` directory.
+
+The workspace has the following two directories:
+
+- `apps`: this directory holds the different documentation websites and projects.
+ - `book`: includes the codebase for the [main Medusa documentation](https://docs.medusajs.com//index.html.md). It's built with [Next.js 15](https://nextjs.org/).
+ - `resources`: includes the codebase for the resources documentation, which powers different sections of the docs such as the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) or [How-to & Tutorials](https://docs.medusajs.com/resources/how-to-tutorials/index.html.md) sections. It's built with [Next.js 15](https://nextjs.org/).
+ - `api-reference`: includes the codebase for the API reference website. It's built with [Next.js 15](https://nextjs.org/).
+ - `ui`: includes the codebase for the Medusa UI documentation website. It's built with [Next.js 15](https://nextjs.org/).
+- `packages`: this directory holds the shared packages and components necessary for the development of the projects in the `apps` directory.
+ - `docs-ui` includes the shared React components between the different apps.
+ - `remark-rehype-plugins` includes Remark and Rehype plugins used by the documentation projects.
+
+***
+
+## Documentation Content
+
+All documentation projects are built with Next.js. The content is writtin in MDX files.
+
+### Medusa Main Docs Content
+
+The content of the Medusa main docs are under the `www/apps/book/app` directory.
+
+### Medusa Resources Content
+
+The content of all pages under the `/resources` path are under the `www/apps/resources/app` directory.
+
+Documentation pages under the `www/apps/resources/references` directory are generated automatically from the source code under the `packages/medusa` directory. So, you can't directly make changes to them. Instead, you'll have to make changes to the comments in the original source code.
+
+### API Reference
+
+The API reference's content is split into two types:
+
+1. Static content, which are the content related to getting started, expanding fields, and more. These are located in the `www/apps/api-reference/markdown` directory. They are MDX files.
+2. OpenAPI specs that are shown to developers when checking the reference of an API Route. These are generated from OpenApi Spec comments, which are under the `www/utils/generated/oas-output` directory.
+
+### Medusa UI Documentation
+
+The content of the Medusa UI documentation are located under the `www/apps/ui/src/content/docs` directory. They are MDX files.
+
+The UI documentation also shows code examples, which are under the `www/apps/ui/src/examples` directory.
+
+The UI component props are generated from the source code and placed into the `www/apps/ui/src/specs` directory. To contribute to these props and their comments, check the comments in the source code under the `packages/design-system/ui` directory.
+
+***
+
+## Style Guide
+
+When you contribute to the documentation content, make sure to follow the [documentation style guide](https://www.notion.so/Style-Guide-Docs-fad86dd1c5f84b48b145e959f36628e0).
+
+***
+
+## How to Contribute
+
+If you’re fixing errors in an existing documentation page, you can scroll down to the end of the page and click on the “Edit this page” link. You’ll be redirected to the GitHub edit form of that page and you can make edits directly and submit a pull request (PR).
+
+If you’re adding a new page or contributing to the codebase, fork the repository, create a new branch, and make all changes necessary in your repository. Then, once you’re done, create a PR in the Medusa repository.
+
+### Base Branch
+
+When you make an edit to an existing documentation page or fork the repository to make changes to the documentation, create a new branch.
+
+Documentation contributions always use `develop` as the base branch. Make sure to also open your PR against the `develop` branch.
+
+### Branch Name
+
+Make sure that the branch name starts with `docs/`. For example, `docs/fix-services`. Vercel deployed previews are only triggered for branches starting with `docs/`.
+
+### Pull Request Conventions
+
+When you create a pull request, prefix the title with `docs:` or `docs(PROJECT_NAME):`, where `PROJECT_NAME` is the name of the documentation project this pull request pertains to. For example, `docs(ui): fix titles`.
+
+In the body of the PR, explain clearly what the PR does. If the PR solves an issue, use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) with the issue number. For example, “Closes #1333”.
+
+***
+
+## Images
+
+If you are adding images to a documentation page, you can host the image on [Imgur](https://imgur.com) for free to include it in the PR. Our team will later upload it to our image hosting.
+
+***
+
+## NPM and Yarn Code Blocks
+
+If you’re adding code blocks that use NPM and Yarn, you must add the `npm2yarn` meta field.
+
+For example:
+
+````md
+```bash npm2yarn
+npm run start
+```
+````
+
+The code snippet must be written using NPM.
+
+### Global Option
+
+When a command uses the global option `-g`, add it at the end of the NPM command to ensure that it’s transformed to a Yarn command properly. For example:
+
+```bash npm2yarn
+npm install @medusajs/cli -g
+```
+
+***
+
+## Linting with Vale
+
+Medusa uses [Vale](https://vale.sh/) to lint documentation pages and perform checks on incoming PRs into the repository.
+
+### Result of Vale PR Checks
+
+You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix.
+
+### Run Vale Locally
+
+If you want to check your work locally, you can do that by:
+
+1. [Installing Vale](https://vale.sh/docs/vale-cli/installation/) on your machine.
+2. Changing to the `www/vale` directory:
+
+```bash
+cd www/vale
+```
+
+3\. Running the `run-vale` script:
+
+```bash
+# to lint content for the main documentation
+./run-vale.sh book/app/learn error resources
+# to lint content for the resources documentation
+./run-vale.sh resources/app error
+# to lint content for the API reference
+./run-vale.sh api-reference/markdown error
+# to lint content for the Medusa UI documentation
+./run-vale.sh ui/src/content/docs error
+# to lint content for the user guide
+./run-vale.sh user-guide/app error
+```
+
+{/* TODO need to enable MDX v1 comments first. */}
+
+{/* ### Linter Exceptions
+
+If it's needed to break some style guide rules in a document, you can wrap the parts that the linter shouldn't scan with the following comments in the `md` or `mdx` files:
+
+```md
+
+
+content that shouldn't be scanned for errors here...
+
+
+```
+
+You can also disable specific rules. For example:
+
+```md
+
+
+Medusa supports Node versions 14 and 16.
+
+
+```
+
+If you use this in your PR, you must justify its usage. */}
+
+***
+
+## Linting with ESLint
+
+Medusa uses ESlint to lint code blocks both in the content and the code base of the documentation apps.
+
+### Linting Content with ESLint
+
+Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `content-eslint`.
+
+If you want to check content ESLint errors locally and fix them, you can do that by:
+
+1\. Install the dependencies in the `www` directory:
+
+```bash
+yarn install
+```
+
+2\. Run the turbo command in the `www` directory:
+
+```bash
+turbo run lint:content
+```
+
+This will fix any fixable errors, and show errors that require your action.
+
+### Linting Code with ESLint
+
+Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `code-docs-eslint`.
+
+If you want to check code ESLint errors locally and fix them, you can do that by:
+
+1\. Install the dependencies in the `www` directory:
+
+```bash
+yarn install
+```
+
+2\. Run the turbo command in the `www` directory:
+
+```bash
+yarn lint
+```
+
+This will fix any fixable errors, and show errors that require your action.
+
+{/* TODO need to enable MDX v1 comments first. */}
+
+{/* ### ESLint Exceptions
+
+If some code blocks have errors that can't or shouldn't be fixed, you can add the following command before the code block:
+
+~~~md
+
+
+```js
+console.log("This block isn't linted")
+```
+
+```js
+console.log("This block is linted")
+```
+~~~
+
+You can also disable specific rules. For example:
+
+~~~md
+
+
+```js
+console.log("This block can use semicolons");
+```
+
+```js
+console.log("This block can't use semi colons")
+```
+~~~ */}
+
+
# Example: Write Integration Tests for API Routes
In this chapter, you'll learn how to write integration tests for API routes using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framework.
@@ -16795,6 +17076,76 @@ const response = await api.post(`/custom`, form, {
```
+# Example: Integration Tests for a Module
+
+In this chapter, find an example of writing an integration test for a module using [moduleIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/modules-tests/index.html.md) from Medusa's Testing Framework.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## Write Integration Test for Module
+
+Consider a `blog` module with a `BlogModuleService` that has a `getMessage` method:
+
+```ts title="src/modules/blog/service.ts"
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
+
+class BlogModuleService extends MedusaService({
+ MyCustom,
+}){
+ getMessage(): string {
+ return "Hello, World!"
+ }
+}
+
+export default BlogModuleService
+```
+
+To create an integration test for the method, create the file `src/modules/blog/__tests__/service.spec.ts` with the following content:
+
+```ts title="src/modules/blog/__tests__/service.spec.ts"
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import { BLOG_MODULE } from ".."
+import BlogModuleService from "../service"
+import MyCustom from "../models/my-custom"
+
+moduleIntegrationTestRunner({
+ moduleName: BLOG_MODULE,
+ moduleModels: [MyCustom],
+ resolve: "./src/modules/blog",
+ testSuite: ({ service }) => {
+ describe("BlogModuleService", () => {
+ it("says hello world", () => {
+ const message = service.getMessage()
+
+ expect(message).toEqual("Hello, World!")
+ })
+ })
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+You use the `moduleIntegrationTestRunner` function to add tests for the `blog` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string.
+
+***
+
+## Run Test
+
+Run the following command to run your module integration tests:
+
+```bash npm2yarn
+npm run test:integration:modules
+```
+
+If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
+
+
# Example: Write Integration Tests for Workflows
In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork.
@@ -16926,340 +17277,6 @@ The `errors` property contains an array of errors thrown during the execution of
If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`.
-# Docs Contribution Guidelines
-
-Thank you for your interest in contributing to the documentation! You will be helping the open source community and other developers interested in learning more about Medusa and using it.
-
-This guide is specific to contributing to the documentation. If you’re interested in contributing to Medusa’s codebase, check out the [contributing guidelines in the Medusa GitHub repository](https://github.com/medusajs/medusa/blob/develop/CONTRIBUTING.md).
-
-## What Can You Contribute?
-
-You can contribute to the Medusa documentation in the following ways:
-
-- Fixes to existing content. This includes small fixes like typos, or adding missing information.
-- Additions to the documentation. If you think a documentation page can be useful to other developers, you can contribute by adding it.
- - Make sure to open an issue first in the [medusa repository](https://github.com/medusajs/medusa) to confirm that you can add that documentation page.
-- Fixes to UI components and tooling. If you find a bug while browsing the documentation, you can contribute by fixing it.
-
-***
-
-## Documentation Workspace
-
-Medusa's documentation projects are all part of the documentation yarn workspace, which you can find in the [medusa repository](https://github.com/medusajs/medusa) under the `www` directory.
-
-The workspace has the following two directories:
-
-- `apps`: this directory holds the different documentation websites and projects.
- - `book`: includes the codebase for the [main Medusa documentation](https://docs.medusajs.com//index.html.md). It's built with [Next.js 15](https://nextjs.org/).
- - `resources`: includes the codebase for the resources documentation, which powers different sections of the docs such as the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) or [How-to & Tutorials](https://docs.medusajs.com/resources/how-to-tutorials/index.html.md) sections. It's built with [Next.js 15](https://nextjs.org/).
- - `api-reference`: includes the codebase for the API reference website. It's built with [Next.js 15](https://nextjs.org/).
- - `ui`: includes the codebase for the Medusa UI documentation website. It's built with [Next.js 15](https://nextjs.org/).
-- `packages`: this directory holds the shared packages and components necessary for the development of the projects in the `apps` directory.
- - `docs-ui` includes the shared React components between the different apps.
- - `remark-rehype-plugins` includes Remark and Rehype plugins used by the documentation projects.
-
-***
-
-## Documentation Content
-
-All documentation projects are built with Next.js. The content is writtin in MDX files.
-
-### Medusa Main Docs Content
-
-The content of the Medusa main docs are under the `www/apps/book/app` directory.
-
-### Medusa Resources Content
-
-The content of all pages under the `/resources` path are under the `www/apps/resources/app` directory.
-
-Documentation pages under the `www/apps/resources/references` directory are generated automatically from the source code under the `packages/medusa` directory. So, you can't directly make changes to them. Instead, you'll have to make changes to the comments in the original source code.
-
-### API Reference
-
-The API reference's content is split into two types:
-
-1. Static content, which are the content related to getting started, expanding fields, and more. These are located in the `www/apps/api-reference/markdown` directory. They are MDX files.
-2. OpenAPI specs that are shown to developers when checking the reference of an API Route. These are generated from OpenApi Spec comments, which are under the `www/utils/generated/oas-output` directory.
-
-### Medusa UI Documentation
-
-The content of the Medusa UI documentation are located under the `www/apps/ui/src/content/docs` directory. They are MDX files.
-
-The UI documentation also shows code examples, which are under the `www/apps/ui/src/examples` directory.
-
-The UI component props are generated from the source code and placed into the `www/apps/ui/src/specs` directory. To contribute to these props and their comments, check the comments in the source code under the `packages/design-system/ui` directory.
-
-***
-
-## Style Guide
-
-When you contribute to the documentation content, make sure to follow the [documentation style guide](https://www.notion.so/Style-Guide-Docs-fad86dd1c5f84b48b145e959f36628e0).
-
-***
-
-## How to Contribute
-
-If you’re fixing errors in an existing documentation page, you can scroll down to the end of the page and click on the “Edit this page” link. You’ll be redirected to the GitHub edit form of that page and you can make edits directly and submit a pull request (PR).
-
-If you’re adding a new page or contributing to the codebase, fork the repository, create a new branch, and make all changes necessary in your repository. Then, once you’re done, create a PR in the Medusa repository.
-
-### Base Branch
-
-When you make an edit to an existing documentation page or fork the repository to make changes to the documentation, create a new branch.
-
-Documentation contributions always use `develop` as the base branch. Make sure to also open your PR against the `develop` branch.
-
-### Branch Name
-
-Make sure that the branch name starts with `docs/`. For example, `docs/fix-services`. Vercel deployed previews are only triggered for branches starting with `docs/`.
-
-### Pull Request Conventions
-
-When you create a pull request, prefix the title with `docs:` or `docs(PROJECT_NAME):`, where `PROJECT_NAME` is the name of the documentation project this pull request pertains to. For example, `docs(ui): fix titles`.
-
-In the body of the PR, explain clearly what the PR does. If the PR solves an issue, use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) with the issue number. For example, “Closes #1333”.
-
-***
-
-## Images
-
-If you are adding images to a documentation page, you can host the image on [Imgur](https://imgur.com) for free to include it in the PR. Our team will later upload it to our image hosting.
-
-***
-
-## NPM and Yarn Code Blocks
-
-If you’re adding code blocks that use NPM and Yarn, you must add the `npm2yarn` meta field.
-
-For example:
-
-````md
-```bash npm2yarn
-npm run start
-```
-````
-
-The code snippet must be written using NPM.
-
-### Global Option
-
-When a command uses the global option `-g`, add it at the end of the NPM command to ensure that it’s transformed to a Yarn command properly. For example:
-
-```bash npm2yarn
-npm install @medusajs/cli -g
-```
-
-***
-
-## Linting with Vale
-
-Medusa uses [Vale](https://vale.sh/) to lint documentation pages and perform checks on incoming PRs into the repository.
-
-### Result of Vale PR Checks
-
-You can check the result of running the "lint" action on your PR by clicking the Details link next to it. You can find there all errors that you need to fix.
-
-### Run Vale Locally
-
-If you want to check your work locally, you can do that by:
-
-1. [Installing Vale](https://vale.sh/docs/vale-cli/installation/) on your machine.
-2. Changing to the `www/vale` directory:
-
-```bash
-cd www/vale
-```
-
-3\. Running the `run-vale` script:
-
-```bash
-# to lint content for the main documentation
-./run-vale.sh book/app/learn error resources
-# to lint content for the resources documentation
-./run-vale.sh resources/app error
-# to lint content for the API reference
-./run-vale.sh api-reference/markdown error
-# to lint content for the Medusa UI documentation
-./run-vale.sh ui/src/content/docs error
-# to lint content for the user guide
-./run-vale.sh user-guide/app error
-```
-
-{/* TODO need to enable MDX v1 comments first. */}
-
-{/* ### Linter Exceptions
-
-If it's needed to break some style guide rules in a document, you can wrap the parts that the linter shouldn't scan with the following comments in the `md` or `mdx` files:
-
-```md
-
-
-content that shouldn't be scanned for errors here...
-
-
-```
-
-You can also disable specific rules. For example:
-
-```md
-
-
-Medusa supports Node versions 14 and 16.
-
-
-```
-
-If you use this in your PR, you must justify its usage. */}
-
-***
-
-## Linting with ESLint
-
-Medusa uses ESlint to lint code blocks both in the content and the code base of the documentation apps.
-
-### Linting Content with ESLint
-
-Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `content-eslint`.
-
-If you want to check content ESLint errors locally and fix them, you can do that by:
-
-1\. Install the dependencies in the `www` directory:
-
-```bash
-yarn install
-```
-
-2\. Run the turbo command in the `www` directory:
-
-```bash
-turbo run lint:content
-```
-
-This will fix any fixable errors, and show errors that require your action.
-
-### Linting Code with ESLint
-
-Each PR runs through a check that lints the code in the content files using ESLint. The action's name is `code-docs-eslint`.
-
-If you want to check code ESLint errors locally and fix them, you can do that by:
-
-1\. Install the dependencies in the `www` directory:
-
-```bash
-yarn install
-```
-
-2\. Run the turbo command in the `www` directory:
-
-```bash
-yarn lint
-```
-
-This will fix any fixable errors, and show errors that require your action.
-
-{/* TODO need to enable MDX v1 comments first. */}
-
-{/* ### ESLint Exceptions
-
-If some code blocks have errors that can't or shouldn't be fixed, you can add the following command before the code block:
-
-~~~md
-
-
-```js
-console.log("This block isn't linted")
-```
-
-```js
-console.log("This block is linted")
-```
-~~~
-
-You can also disable specific rules. For example:
-
-~~~md
-
-
-```js
-console.log("This block can use semicolons");
-```
-
-```js
-console.log("This block can't use semi colons")
-```
-~~~ */}
-
-
-# Example: Integration Tests for a Module
-
-In this chapter, find an example of writing an integration test for a module using [moduleIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/modules-tests/index.html.md) from Medusa's Testing Framework.
-
-### Prerequisites
-
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-
-## Write Integration Test for Module
-
-Consider a `blog` module with a `BlogModuleService` that has a `getMessage` method:
-
-```ts title="src/modules/blog/service.ts"
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
-
-class BlogModuleService extends MedusaService({
- MyCustom,
-}){
- getMessage(): string {
- return "Hello, World!"
- }
-}
-
-export default BlogModuleService
-```
-
-To create an integration test for the method, create the file `src/modules/blog/__tests__/service.spec.ts` with the following content:
-
-```ts title="src/modules/blog/__tests__/service.spec.ts"
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import { BLOG_MODULE } from ".."
-import BlogModuleService from "../service"
-import MyCustom from "../models/my-custom"
-
-moduleIntegrationTestRunner({
- moduleName: BLOG_MODULE,
- moduleModels: [MyCustom],
- resolve: "./src/modules/blog",
- testSuite: ({ service }) => {
- describe("BlogModuleService", () => {
- it("says hello world", () => {
- const message = service.getMessage()
-
- expect(message).toEqual("Hello, World!")
- })
- })
- },
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-You use the `moduleIntegrationTestRunner` function to add tests for the `blog` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string.
-
-***
-
-## Run Test
-
-Run the following command to run your module integration tests:
-
-```bash npm2yarn
-npm run test:integration:modules
-```
-
-If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
-
-
# Commerce Modules
In this section of the documentation, you'll find guides and references related to Medusa's commerce modules.
@@ -17300,6 +17317,286 @@ The Commerce Modules can be used in many use cases, including:
- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
+# Cart Module
+
+In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application.
+
+Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Cart Features
+
+- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more.
+- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
+- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods.
+- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer.
+
+***
+
+## How to Use the Cart Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-cart.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createCartStep = createStep(
+ "create-cart",
+ async ({}, { container }) => {
+ const cartModuleService = container.resolve(Modules.CART)
+
+ const cart = await cartModuleService.createCarts({
+ currency_code: "usd",
+ shipping_address: {
+ address_1: "1512 Barataria Blvd",
+ country_code: "us",
+ },
+ items: [
+ {
+ title: "Shirt",
+ unit_price: 1000,
+ quantity: 1,
+ },
+ ],
+ })
+
+ return new StepResponse({ cart }, cart.id)
+ },
+ async (cartId, { container }) => {
+ if (!cartId) {
+ return
+ }
+ const cartModuleService = container.resolve(Modules.CART)
+
+ await cartModuleService.deleteCarts([cartId])
+ }
+)
+
+export const createCartWorkflow = createWorkflow(
+ "create-cart",
+ () => {
+ const { cart } = createCartStep()
+
+ return new WorkflowResponse({
+ cart,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createCartWorkflow } from "../../workflows/create-cart"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createCartWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createCartWorkflow } from "../workflows/create-cart"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createCartWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createCartWorkflow } from "../workflows/create-cart"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createCartWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
+# Auth Module
+
+In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
+
+Medusa has auth related features available out-of-the-box through the Auth Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Auth Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Auth Features
+
+- [Basic User Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#1-basic-authentication-flow/index.html.md): Authenticate users using their email and password credentials.
+- [Third-Party and Social Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md): Authenticate users using third-party services and social platforms, such as [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) and [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md).
+- [Authenticate Custom Actor Types](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md): Create custom user or actor types, such as managers, authenticate them in your application, and guard routes based on the custom user types.
+- [Custom Authentication Providers](https://docs.medusajs.com/references/auth/provider/index.html.md): Integrate third-party services with custom authentication providors.
+
+***
+
+## How to Use the Auth Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules, MedusaError } from "@medusajs/framework/utils"
+import { MedusaRequest } from "@medusajs/framework/http"
+import { AuthenticationInput } from "@medusajs/framework/types"
+
+type Input = {
+ req: MedusaRequest
+}
+
+const authenticateUserStep = createStep(
+ "authenticate-user",
+ async ({ req }: Input, { container }) => {
+ const authModuleService = container.resolve(Modules.AUTH)
+
+ const { success, authIdentity, error } = await authModuleService
+ .authenticate(
+ "emailpass",
+ {
+ url: req.url,
+ headers: req.headers,
+ query: req.query,
+ body: req.body,
+ authScope: "admin", // or custom actor type
+ protocol: req.protocol,
+ } as AuthenticationInput
+ )
+
+ if (!success) {
+ // incorrect authentication details
+ throw new MedusaError(
+ MedusaError.Types.UNAUTHORIZED,
+ error || "Incorrect authentication details"
+ )
+ }
+
+ return new StepResponse({ authIdentity }, authIdentity?.id)
+ },
+ async (authIdentityId, { container }) => {
+ if (!authIdentityId) {
+ return
+ }
+
+ const authModuleService = container.resolve(Modules.AUTH)
+
+ await authModuleService.deleteAuthIdentities([authIdentityId])
+ }
+)
+
+export const authenticateUserWorkflow = createWorkflow(
+ "authenticate-user",
+ (input: Input) => {
+ const { authIdentity } = authenticateUserStep(input)
+
+ return new WorkflowResponse({
+ authIdentity,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await authenticateUserWorkflow(req.scope)
+ .run({
+ req,
+ })
+
+ res.send(result)
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+## Configure Auth Module
+
+The Auth Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/module-options/index.html.md) for details on the module's options.
+
+***
+
+## Providers
+
+Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
+
+***
+
+
# API Key Module
In this section of the documentation, you will find resources to learn more about the API Key Module and how to use it in your application.
@@ -17440,24 +17737,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Cart Module
+# Customer Module
-In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application.
-Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers and groups using the dashboard.
+
+Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Cart Features
+## Customer Features
-- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more.
-- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
-- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods.
-- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer.
+- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store.
+- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md).
***
-## How to Use the Cart Module
+## How to Use the Customer Module
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -17465,7 +17762,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-cart.ts" highlights={highlights}
+```ts title="src/workflows/create-customer.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -17474,45 +17771,36 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createCartStep = createStep(
- "create-cart",
+const createCustomerStep = createStep(
+ "create-customer",
async ({}, { container }) => {
- const cartModuleService = container.resolve(Modules.CART)
+ const customerModuleService = container.resolve(Modules.CUSTOMER)
- const cart = await cartModuleService.createCarts({
- currency_code: "usd",
- shipping_address: {
- address_1: "1512 Barataria Blvd",
- country_code: "us",
- },
- items: [
- {
- title: "Shirt",
- unit_price: 1000,
- quantity: 1,
- },
- ],
+ const customer = await customerModuleService.createCustomers({
+ first_name: "Peter",
+ last_name: "Hayes",
+ email: "peter.hayes@example.com",
})
- return new StepResponse({ cart }, cart.id)
+ return new StepResponse({ customer }, customer.id)
},
- async (cartId, { container }) => {
- if (!cartId) {
+ async (customerId, { container }) => {
+ if (!customerId) {
return
}
- const cartModuleService = container.resolve(Modules.CART)
+ const customerModuleService = container.resolve(Modules.CUSTOMER)
- await cartModuleService.deleteCarts([cartId])
+ await customerModuleService.deleteCustomers([customerId])
}
)
-export const createCartWorkflow = createWorkflow(
- "create-cart",
+export const createCustomerWorkflow = createWorkflow(
+ "create-customer",
() => {
- const { cart } = createCartStep()
+ const { customer } = createCustomerStep()
return new WorkflowResponse({
- cart,
+ customer,
})
}
)
@@ -17527,13 +17815,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createCartWorkflow } from "../../workflows/create-cart"
+import { createCustomerWorkflow } from "../../workflows/create-customer"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createCartWorkflow(req.scope)
+ const { result } = await createCustomerWorkflow(req.scope)
.run()
res.send(result)
@@ -17547,13 +17835,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createCartWorkflow } from "../workflows/create-cart"
+import { createCustomerWorkflow } from "../workflows/create-customer"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createCartWorkflow(container)
+ const { result } = await createCustomerWorkflow(container)
.run()
console.log(result)
@@ -17568,12 +17856,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createCartWorkflow } from "../workflows/create-cart"
+import { createCustomerWorkflow } from "../workflows/create-customer"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createCartWorkflow(container)
+ const { result } = await createCustomerWorkflow(container)
.run()
console.log(result)
@@ -17756,425 +18044,6 @@ The Fulfillment Module accepts options for further configurations. Refer to [thi
***
-# Currency Module
-
-In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store's currencies using the dashboard.
-
-Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Currency Features
-
-- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them.
-- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
-
-***
-
-## How to Use the Currency Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const retrieveCurrencyStep = createStep(
- "retrieve-currency",
- async ({}, { container }) => {
- const currencyModuleService = container.resolve(Modules.CURRENCY)
-
- const currency = await currencyModuleService
- .retrieveCurrency("usd")
-
- return new StepResponse({ currency })
- }
-)
-
-type Input = {
- price: number
-}
-
-export const retrievePriceWithCurrency = createWorkflow(
- "create-currency",
- (input: Input) => {
- const { currency } = retrieveCurrencyStep()
-
- const formattedPrice = transform({
- input,
- currency,
- }, (data) => {
- return `${data.currency.symbol}${data.input.price}`
- })
-
- return new WorkflowResponse({
- formattedPrice,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await retrievePriceWithCurrency(req.scope)
- .run({
- price: 10,
- })
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
-# Customer Module
-
-In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers and groups using the dashboard.
-
-Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Customer Features
-
-- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store.
-- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md).
-
-***
-
-## How to Use the Customer Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-customer.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createCustomerStep = createStep(
- "create-customer",
- async ({}, { container }) => {
- const customerModuleService = container.resolve(Modules.CUSTOMER)
-
- const customer = await customerModuleService.createCustomers({
- first_name: "Peter",
- last_name: "Hayes",
- email: "peter.hayes@example.com",
- })
-
- return new StepResponse({ customer }, customer.id)
- },
- async (customerId, { container }) => {
- if (!customerId) {
- return
- }
- const customerModuleService = container.resolve(Modules.CUSTOMER)
-
- await customerModuleService.deleteCustomers([customerId])
- }
-)
-
-export const createCustomerWorkflow = createWorkflow(
- "create-customer",
- () => {
- const { customer } = createCustomerStep()
-
- return new WorkflowResponse({
- customer,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createCustomerWorkflow } from "../../workflows/create-customer"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createCustomerWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createCustomerWorkflow } from "../workflows/create-customer"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createCustomerWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createCustomerWorkflow } from "../workflows/create-customer"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createCustomerWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
-# Auth Module
-
-In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
-
-Medusa has auth related features available out-of-the-box through the Auth Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Auth Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Auth Features
-
-- [Basic User Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#1-basic-authentication-flow/index.html.md): Authenticate users using their email and password credentials.
-- [Third-Party and Social Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md): Authenticate users using third-party services and social platforms, such as [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) and [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md).
-- [Authenticate Custom Actor Types](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md): Create custom user or actor types, such as managers, authenticate them in your application, and guard routes based on the custom user types.
-- [Custom Authentication Providers](https://docs.medusajs.com/references/auth/provider/index.html.md): Integrate third-party services with custom authentication providors.
-
-***
-
-## How to Use the Auth Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules, MedusaError } from "@medusajs/framework/utils"
-import { MedusaRequest } from "@medusajs/framework/http"
-import { AuthenticationInput } from "@medusajs/framework/types"
-
-type Input = {
- req: MedusaRequest
-}
-
-const authenticateUserStep = createStep(
- "authenticate-user",
- async ({ req }: Input, { container }) => {
- const authModuleService = container.resolve(Modules.AUTH)
-
- const { success, authIdentity, error } = await authModuleService
- .authenticate(
- "emailpass",
- {
- url: req.url,
- headers: req.headers,
- query: req.query,
- body: req.body,
- authScope: "admin", // or custom actor type
- protocol: req.protocol,
- } as AuthenticationInput
- )
-
- if (!success) {
- // incorrect authentication details
- throw new MedusaError(
- MedusaError.Types.UNAUTHORIZED,
- error || "Incorrect authentication details"
- )
- }
-
- return new StepResponse({ authIdentity }, authIdentity?.id)
- },
- async (authIdentityId, { container }) => {
- if (!authIdentityId) {
- return
- }
-
- const authModuleService = container.resolve(Modules.AUTH)
-
- await authModuleService.deleteAuthIdentities([authIdentityId])
- }
-)
-
-export const authenticateUserWorkflow = createWorkflow(
- "authenticate-user",
- (input: Input) => {
- const { authIdentity } = authenticateUserStep(input)
-
- return new WorkflowResponse({
- authIdentity,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await authenticateUserWorkflow(req.scope)
- .run({
- req,
- })
-
- res.send(result)
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-## Configure Auth Module
-
-The Auth Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/module-options/index.html.md) for details on the module's options.
-
-***
-
-## Providers
-
-Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
-
-***
-
-
# Inventory Module
In this section of the documentation, you will find resources to learn more about the Inventory Module and how to use it in your application.
@@ -18319,27 +18188,27 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Pricing Module
+# Order Module
-In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/price-lists/index.html.md) to learn how to manage price lists using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/index.html.md) to learn how to manage orders using the dashboard.
-Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
+Medusa has order related features available out-of-the-box through the Order Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Order Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Pricing Features
+## Order Features
-- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
-- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
-- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
-- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
-- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
+- [Order Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/concepts/index.html.md): Store and manage your orders to retrieve, create, cancel, and perform other operations.
+- Draft Orders: Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
+- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/promotion-adjustments/index.html.md): Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
+- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/tax-lines/index.html.md): Apply tax lines to an order's line items and shipping methods.
+- [Returns](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md), [Edits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md), [Exchanges](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md), and [Claims](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md): Make [changes](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-change/index.html.md) to an order to edit, return, or exchange its items, with [version-based control](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-versioning/index.html.md) over the order's timeline.
***
-## How to Use the Pricing Module
+## How to Use the Order Module
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -18347,7 +18216,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-price-set.ts" highlights={highlights}
+```ts title="src/workflows/create-draft-order.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -18356,46 +18225,48 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createPriceSetStep = createStep(
- "create-price-set",
+const createDraftOrderStep = createStep(
+ "create-order",
async ({}, { container }) => {
- const pricingModuleService = container.resolve(Modules.PRICING)
+ const orderModuleService = container.resolve(Modules.ORDER)
- const priceSet = await pricingModuleService.createPriceSets({
- prices: [
+ const draftOrder = await orderModuleService.createOrders({
+ currency_code: "usd",
+ items: [
{
- amount: 500,
- currency_code: "USD",
- },
- {
- amount: 400,
- currency_code: "EUR",
- min_quantity: 0,
- max_quantity: 4,
- rules: {},
+ title: "Shirt",
+ quantity: 1,
+ unit_price: 3000,
},
],
+ shipping_methods: [
+ {
+ name: "Express shipping",
+ amount: 3000,
+ },
+ ],
+ status: "draft",
})
- return new StepResponse({ priceSet }, priceSet.id)
+ return new StepResponse({ draftOrder }, draftOrder.id)
},
- async (priceSetId, { container }) => {
- if (!priceSetId) {
+ async (draftOrderId, { container }) => {
+ if (!draftOrderId) {
return
}
- const pricingModuleService = container.resolve(Modules.PRICING)
+ const orderModuleService = container.resolve(Modules.ORDER)
- await pricingModuleService.deletePriceSets([priceSetId])
+ await orderModuleService.deleteOrders([draftOrderId])
}
)
-export const createPriceSetWorkflow = createWorkflow(
- "create-price-set",
+export const createDraftOrderWorkflow = createWorkflow(
+ "create-draft-order",
() => {
- const { priceSet } = createPriceSetStep()
+ const { draftOrder } = createDraftOrderStep()
return new WorkflowResponse({
- priceSet,
+ draftOrder,
})
}
)
@@ -18410,13 +18281,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createPriceSetWorkflow } from "../../workflows/create-price-set"
+import { createDraftOrderWorkflow } from "../../workflows/create-draft-order"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createPriceSetWorkflow(req.scope)
+ const { result } = await createDraftOrderWorkflow(req.scope)
.run()
res.send(result)
@@ -18430,13 +18301,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createPriceSetWorkflow } from "../workflows/create-price-set"
+import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createPriceSetWorkflow(container)
+ const { result } = await createDraftOrderWorkflow(container)
.run()
console.log(result)
@@ -18451,12 +18322,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createPriceSetWorkflow } from "../workflows/create-price-set"
+import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createPriceSetWorkflow(container)
+ const { result } = await createDraftOrderWorkflow(container)
.run()
console.log(result)
@@ -18473,25 +18344,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Product Module
+# Currency Module
-In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/index.html.md) to learn how to manage products using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store's currencies using the dashboard.
-Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module.
+Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Product Features
+## Currency Features
-- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
-- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
-- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
+- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them.
+- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
***
-## How to Use the Product Module
+## How to Use the Currency Module
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -18499,57 +18369,46 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-product.ts" highlights={highlights}
+```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
createStep,
StepResponse,
+ transform,
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createProductStep = createStep(
- "create-product",
+const retrieveCurrencyStep = createStep(
+ "retrieve-currency",
async ({}, { container }) => {
- const productService = container.resolve(Modules.PRODUCT)
+ const currencyModuleService = container.resolve(Modules.CURRENCY)
- const product = await productService.createProducts({
- title: "Medusa Shirt",
- options: [
- {
- title: "Color",
- values: ["Black", "White"],
- },
- ],
- variants: [
- {
- title: "Black Shirt",
- options: {
- Color: "Black",
- },
- },
- ],
- })
+ const currency = await currencyModuleService
+ .retrieveCurrency("usd")
- return new StepResponse({ product }, product.id)
- },
- async (productId, { container }) => {
- if (!productId) {
- return
- }
- const productService = container.resolve(Modules.PRODUCT)
-
- await productService.deleteProducts([productId])
+ return new StepResponse({ currency })
}
)
-export const createProductWorkflow = createWorkflow(
- "create-product",
- () => {
- const { product } = createProductStep()
+type Input = {
+ price: number
+}
+
+export const retrievePriceWithCurrency = createWorkflow(
+ "create-currency",
+ (input: Input) => {
+ const { currency } = retrieveCurrencyStep()
+
+ const formattedPrice = transform({
+ input,
+ currency,
+ }, (data) => {
+ return `${data.currency.symbol}${data.input.price}`
+ })
return new WorkflowResponse({
- product,
+ formattedPrice,
})
}
)
@@ -18559,19 +18418,21 @@ You can then execute the workflow in your custom API routes, scheduled jobs, or
### API Route
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createProductWorkflow } from "../../workflows/create-product"
+import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createProductWorkflow(req.scope)
- .run()
+ const { result } = await retrievePriceWithCurrency(req.scope)
+ .run({
+ price: 10,
+ })
res.send(result)
}
@@ -18579,19 +18440,21 @@ export async function GET(
### Subscriber
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createProductWorkflow } from "../workflows/create-product"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createProductWorkflow(container)
- .run()
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
console.log(result)
}
@@ -18603,15 +18466,17 @@ export const config: SubscriberConfig = {
### Scheduled Job
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createProductWorkflow } from "../workflows/create-product"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createProductWorkflow(container)
- .run()
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
console.log(result)
}
@@ -18782,27 +18647,27 @@ Medusa provides the following payment providers out-of-the-box. You can use them
***
-# Order Module
+# Pricing Module
-In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/index.html.md) to learn how to manage orders using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/price-lists/index.html.md) to learn how to manage price lists using the dashboard.
-Medusa has order related features available out-of-the-box through the Order Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Order Module.
+Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Order Features
+## Pricing Features
-- [Order Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/concepts/index.html.md): Store and manage your orders to retrieve, create, cancel, and perform other operations.
-- Draft Orders: Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
-- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/promotion-adjustments/index.html.md): Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
-- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/tax-lines/index.html.md): Apply tax lines to an order's line items and shipping methods.
-- [Returns](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md), [Edits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md), [Exchanges](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md), and [Claims](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md): Make [changes](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-change/index.html.md) to an order to edit, return, or exchange its items, with [version-based control](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-versioning/index.html.md) over the order's timeline.
+- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
+- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
+- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
+- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
+- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
***
-## How to Use the Order Module
+## How to Use the Pricing Module
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -18810,7 +18675,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-draft-order.ts" highlights={highlights}
+```ts title="src/workflows/create-price-set.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -18819,48 +18684,46 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createDraftOrderStep = createStep(
- "create-order",
+const createPriceSetStep = createStep(
+ "create-price-set",
async ({}, { container }) => {
- const orderModuleService = container.resolve(Modules.ORDER)
+ const pricingModuleService = container.resolve(Modules.PRICING)
- const draftOrder = await orderModuleService.createOrders({
- currency_code: "usd",
- items: [
+ const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
{
- title: "Shirt",
- quantity: 1,
- unit_price: 3000,
+ amount: 500,
+ currency_code: "USD",
+ },
+ {
+ amount: 400,
+ currency_code: "EUR",
+ min_quantity: 0,
+ max_quantity: 4,
+ rules: {},
},
],
- shipping_methods: [
- {
- name: "Express shipping",
- amount: 3000,
- },
- ],
- status: "draft",
})
- return new StepResponse({ draftOrder }, draftOrder.id)
+ return new StepResponse({ priceSet }, priceSet.id)
},
- async (draftOrderId, { container }) => {
- if (!draftOrderId) {
+ async (priceSetId, { container }) => {
+ if (!priceSetId) {
return
}
- const orderModuleService = container.resolve(Modules.ORDER)
+ const pricingModuleService = container.resolve(Modules.PRICING)
- await orderModuleService.deleteOrders([draftOrderId])
+ await pricingModuleService.deletePriceSets([priceSetId])
}
)
-export const createDraftOrderWorkflow = createWorkflow(
- "create-draft-order",
+export const createPriceSetWorkflow = createWorkflow(
+ "create-price-set",
() => {
- const { draftOrder } = createDraftOrderStep()
+ const { priceSet } = createPriceSetStep()
return new WorkflowResponse({
- draftOrder,
+ priceSet,
})
}
)
@@ -18875,13 +18738,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createDraftOrderWorkflow } from "../../workflows/create-draft-order"
+import { createPriceSetWorkflow } from "../../workflows/create-price-set"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createDraftOrderWorkflow(req.scope)
+ const { result } = await createPriceSetWorkflow(req.scope)
.run()
res.send(result)
@@ -18895,13 +18758,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
+import { createPriceSetWorkflow } from "../workflows/create-price-set"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createDraftOrderWorkflow(container)
+ const { result } = await createPriceSetWorkflow(container)
.run()
console.log(result)
@@ -18916,172 +18779,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
+import { createPriceSetWorkflow } from "../workflows/create-price-set"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createDraftOrderWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
-# Sales Channel Module
-
-In this section of the documentation, you will find resources to learn more about the Sales Channel Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/sales-channels/index.html.md) to learn how to manage sales channels using the dashboard.
-
-Medusa has sales channel related features available out-of-the-box through the Sales Channel Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Sales Channel Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## What's a Sales Channel?
-
-A sales channel indicates an online or offline channel that you sell products on.
-
-Some use case examples for using a sales channel:
-
-- Implement a B2B Ecommerce Store.
-- Specify different products for each channel you sell in.
-- Support omnichannel in your ecommerce store.
-
-***
-
-## Sales Channel Features
-
-- [Sales Channel Management](https://docs.medusajs.com/references/sales-channel/models/SalesChannel/index.html.md): Manage sales channels in your store. Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
-- [Product Availability](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Medusa uses the Product and Sales Channel modules to allow merchants to specify a product's availability per sales channel.
-- [Cart and Order Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Carts, available through the Cart Module, are scoped to a sales channel. Paired with the product availability feature, you benefit from more features like allowing only products available in sales channel in a cart.
-- [Inventory Availability Per Sales Channel](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Medusa links sales channels to stock locations, allowing you to retrieve available inventory of products based on the specified sales channel.
-
-***
-
-## How to Use Sales Channel Module's Service
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-sales-channel.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createSalesChannelStep = createStep(
- "create-sales-channel",
- async ({}, { container }) => {
- const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
-
- const salesChannels = await salesChannelModuleService.createSalesChannels([
- {
- name: "B2B",
- },
- {
- name: "Mobile App",
- },
- ])
-
- return new StepResponse({ salesChannels }, salesChannels.map((sc) => sc.id))
- },
- async (salesChannelIds, { container }) => {
- if (!salesChannelIds) {
- return
- }
- const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
-
- await salesChannelModuleService.deleteSalesChannels(
- salesChannelIds
- )
- }
-)
-
-export const createSalesChannelWorkflow = createWorkflow(
- "create-sales-channel",
- () => {
- const { salesChannels } = createSalesChannelStep()
-
- return new WorkflowResponse({
- salesChannels,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createSalesChannelWorkflow } from "../../workflows/create-sales-channel"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createSalesChannelWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createSalesChannelWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createSalesChannelWorkflow(container)
+ const { result } = await createPriceSetWorkflow(container)
.run()
console.log(result)
@@ -19246,6 +18949,160 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+# Product Module
+
+In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/index.html.md) to learn how to manage products using the dashboard.
+
+Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Product Features
+
+- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
+- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
+- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products.
+
+***
+
+## How to Use the Product Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-product.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createProductStep = createStep(
+ "create-product",
+ async ({}, { container }) => {
+ const productService = container.resolve(Modules.PRODUCT)
+
+ const product = await productService.createProducts({
+ title: "Medusa Shirt",
+ options: [
+ {
+ title: "Color",
+ values: ["Black", "White"],
+ },
+ ],
+ variants: [
+ {
+ title: "Black Shirt",
+ options: {
+ Color: "Black",
+ },
+ },
+ ],
+ })
+
+ return new StepResponse({ product }, product.id)
+ },
+ async (productId, { container }) => {
+ if (!productId) {
+ return
+ }
+ const productService = container.resolve(Modules.PRODUCT)
+
+ await productService.deleteProducts([productId])
+ }
+)
+
+export const createProductWorkflow = createWorkflow(
+ "create-product",
+ () => {
+ const { product } = createProductStep()
+
+ return new WorkflowResponse({
+ product,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createProductWorkflow } from "../../workflows/create-product"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createProductWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createProductWorkflow } from "../workflows/create-product"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createProductWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createProductWorkflow } from "../workflows/create-product"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createProductWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
# Stock Location Module
In this section of the documentation, you will find resources to learn more about the Stock Location Module and how to use it in your application.
@@ -19526,24 +19383,38 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# User Module
+# Sales Channel Module
-In this section of the documentation, you will find resources to learn more about the User Module and how to use it in your application.
+In this section of the documentation, you will find resources to learn more about the Sales Channel Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/users/index.html.md) to learn how to manage users using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/sales-channels/index.html.md) to learn how to manage sales channels using the dashboard.
-Medusa has user related features available out-of-the-box through the User Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this User Module.
+Medusa has sales channel related features available out-of-the-box through the Sales Channel Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Sales Channel Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## User Features
+## What's a Sales Channel?
-- [User Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/user-creation-flows/index.html.md): Store and manage users in your store.
-- [Invite Users](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/user-creation-flows#invite-users/index.html.md): Invite users to join your store and manage those invites.
+A sales channel indicates an online or offline channel that you sell products on.
+
+Some use case examples for using a sales channel:
+
+- Implement a B2B Ecommerce Store.
+- Specify different products for each channel you sell in.
+- Support omnichannel in your ecommerce store.
***
-## How to Use User Module's Service
+## Sales Channel Features
+
+- [Sales Channel Management](https://docs.medusajs.com/references/sales-channel/models/SalesChannel/index.html.md): Manage sales channels in your store. Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
+- [Product Availability](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Medusa uses the Product and Sales Channel modules to allow merchants to specify a product's availability per sales channel.
+- [Cart and Order Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Carts, available through the Cart Module, are scoped to a sales channel. Paired with the product availability feature, you benefit from more features like allowing only products available in sales channel in a cart.
+- [Inventory Availability Per Sales Channel](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/links-to-other-modules/index.html.md): Medusa links sales channels to stock locations, allowing you to retrieve available inventory of products based on the specified sales channel.
+
+***
+
+## How to Use Sales Channel Module's Service
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
@@ -19551,7 +19422,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-user.ts" highlights={highlights}
+```ts title="src/workflows/create-sales-channel.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -19560,36 +19431,41 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createUserStep = createStep(
- "create-user",
+const createSalesChannelStep = createStep(
+ "create-sales-channel",
async ({}, { container }) => {
- const userModuleService = container.resolve(Modules.USER)
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
- const user = await userModuleService.createUsers({
- email: "user@example.com",
- first_name: "John",
- last_name: "Smith",
- })
+ const salesChannels = await salesChannelModuleService.createSalesChannels([
+ {
+ name: "B2B",
+ },
+ {
+ name: "Mobile App",
+ },
+ ])
- return new StepResponse({ user }, user.id)
+ return new StepResponse({ salesChannels }, salesChannels.map((sc) => sc.id))
},
- async (userId, { container }) => {
- if (!userId) {
+ async (salesChannelIds, { container }) => {
+ if (!salesChannelIds) {
return
}
- const userModuleService = container.resolve(Modules.USER)
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
- await userModuleService.deleteUsers([userId])
+ await salesChannelModuleService.deleteSalesChannels(
+ salesChannelIds
+ )
}
)
-export const createUserWorkflow = createWorkflow(
- "create-user",
+export const createSalesChannelWorkflow = createWorkflow(
+ "create-sales-channel",
() => {
- const { user } = createUserStep()
+ const { salesChannels } = createSalesChannelStep()
return new WorkflowResponse({
- user,
+ salesChannels,
})
}
)
@@ -19604,13 +19480,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createUserWorkflow } from "../../workflows/create-user"
+import { createSalesChannelWorkflow } from "../../workflows/create-sales-channel"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createUserWorkflow(req.scope)
+ const { result } = await createSalesChannelWorkflow(req.scope)
.run()
res.send(result)
@@ -19624,13 +19500,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createUserWorkflow } from "../workflows/create-user"
+import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createUserWorkflow(container)
+ const { result } = await createSalesChannelWorkflow(container)
.run()
console.log(result)
@@ -19645,159 +19521,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createUserWorkflow } from "../workflows/create-user"
+import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createUserWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-## Configure User Module
-
-The User Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/module-options/index.html.md) for details on the module's options.
-
-***
-
-
-# Store Module
-
-In this section of the documentation, you will find resources to learn more about the Store Module and how to use it in your application.
-
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store using the dashboard.
-
-Medusa has store related features available out-of-the-box through the Store Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Store Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Store Features
-
-- [Store Management](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create and manage stores in your application.
-- [Multi-Tenancy Support](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create multiple stores, each having its own configurations.
-
-***
-
-## How to Use Store Module's Service
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/create-store.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createStoreStep = createStep(
- "create-store",
- async ({}, { container }) => {
- const storeModuleService = container.resolve(Modules.STORE)
-
- const store = await storeModuleService.createStores({
- name: "My Store",
- supported_currencies: [{
- currency_code: "usd",
- is_default: true,
- }],
- })
-
- return new StepResponse({ store }, store.id)
- },
- async (storeId, { container }) => {
- if(!storeId) {
- return
- }
- const storeModuleService = container.resolve(Modules.STORE)
-
- await storeModuleService.deleteStores([storeId])
- }
-)
-
-export const createStoreWorkflow = createWorkflow(
- "create-store",
- () => {
- const { store } = createStoreStep()
-
- return new WorkflowResponse({ store })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createStoreWorkflow } from "../../workflows/create-store"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createStoreWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createStoreWorkflow } from "../workflows/create-store"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createStoreWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createStoreWorkflow } from "../workflows/create-store"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createStoreWorkflow(container)
+ const { result } = await createSalesChannelWorkflow(container)
.run()
console.log(result)
@@ -19958,131 +19687,152 @@ The Tax Module accepts options for further configurations. Refer to [this docume
***
-# API Key Concepts
+# User Module
-In this document, you’ll learn about the different types of API keys, their expiration and verification.
+In this section of the documentation, you will find resources to learn more about the User Module and how to use it in your application.
-## API Key Types
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/users/index.html.md) to learn how to manage users using the dashboard.
-There are two types of API keys:
+Medusa has user related features available out-of-the-box through the User Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this User Module.
-- `publishable`: A public key used in client applications, such as a storefront.
-- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
+## User Features
+
+- [User Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/user-creation-flows/index.html.md): Store and manage users in your store.
+- [Invite Users](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/user-creation-flows#invite-users/index.html.md): Invite users to join your store and manage those invites.
***
-## API Key Expiration
+## How to Use User Module's Service
-An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md).
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-The associated token is no longer usable or verifiable.
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-***
+For example:
-## Token Verification
-
-To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
-
-
-# Links between API Key Module and Other Modules
-
-This document showcases the module links defined between the API Key Module and other commerce modules.
-
-## Summary
-
-The API Key Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-|| in |Stored||
-
-***
-
-## Sales Channel Module
-
-You can create a publishable API key and associate it with a sales channel. Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
-
-
-
-This is useful to avoid passing the sales channel's ID as a parameter of every request, and instead pass the publishable API key in the header of any request to the Store API route.
-
-Learn more about this in the [Sales Channel Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the sales channels of an API key with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: apiKeys } = await query.graph({
- entity: "api_key",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// apiKeys.sales_channels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: apiKeys } = useQueryGraphStep({
- entity: "api_key",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// apiKeys.sales_channels
-```
-
-### Manage with Link
-
-To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
+```ts title="src/workflows/create-user.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-// ...
+const createUserStep = createStep(
+ "create-user",
+ async ({}, { container }) => {
+ const userModuleService = container.resolve(Modules.USER)
-await link.create({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
+ const user = await userModuleService.createUsers({
+ email: "user@example.com",
+ first_name: "John",
+ last_name: "Smith",
+ })
+
+ return new StepResponse({ user }, user.id)
},
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
+ async (userId, { container }) => {
+ if (!userId) {
+ return
+ }
+ const userModuleService = container.resolve(Modules.USER)
+
+ await userModuleService.deleteUsers([userId])
+ }
+)
+
+export const createUserWorkflow = createWorkflow(
+ "create-user",
+ () => {
+ const { user } = createUserStep()
+
+ return new WorkflowResponse({
+ user,
+ })
+ }
+)
```
-### createRemoteLinkStep
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+### API Route
-// ...
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createUserWorkflow } from "../../workflows/create-user"
-createRemoteLinkStep({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createUserWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
```
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createUserWorkflow } from "../workflows/create-user"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createUserWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createUserWorkflow } from "../workflows/create-user"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createUserWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+## Configure User Module
+
+The User Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/user/module-options/index.html.md) for details on the module's options.
+
+***
+
# Cart Concepts
@@ -20121,80 +19871,146 @@ If the fulfillment provider requires additional custom data to be passed along f
The `data` property is an object used to store custom data relevant later for fulfillment.
-# Tax Lines in Cart Module
+# Store Module
-In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module.
+In this section of the documentation, you will find resources to learn more about the Store Module and how to use it in your application.
-## What are Tax Lines?
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/store/index.html.md) to learn how to manage your store using the dashboard.
-A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+Medusa has store related features available out-of-the-box through the Store Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Store Module.
-
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Store Features
+
+- [Store Management](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create and manage stores in your application.
+- [Multi-Tenancy Support](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create multiple stores, each having its own configurations.
***
-## Tax Inclusivity
+## How to Use Store Module's Service
-By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal.
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+For example:
-The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective.
+```ts title="src/workflows/create-store.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
-
+const createStoreStep = createStep(
+ "create-store",
+ async ({}, { container }) => {
+ const storeModuleService = container.resolve(Modules.STORE)
-For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`.
+ const store = await storeModuleService.createStores({
+ name: "My Store",
+ supported_currencies: [{
+ currency_code: "usd",
+ is_default: true,
+ }],
+ })
-***
+ return new StepResponse({ store }, store.id)
+ },
+ async (storeId, { container }) => {
+ if(!storeId) {
+ return
+ }
+ const storeModuleService = container.resolve(Modules.STORE)
+
+ await storeModuleService.deleteStores([storeId])
+ }
+)
-## Retrieve Tax Lines
+export const createStoreWorkflow = createWorkflow(
+ "create-store",
+ () => {
+ const { store } = createStoreStep()
-When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
-
-```ts
-// retrieve the cart
-const cart = await cartModuleService.retrieveCart("cart_123", {
- relations: [
- "items.tax_lines",
- "shipping_methods.tax_lines",
- "shipping_address",
- ],
-})
-
-// retrieve the tax lines
-const taxLines = await taxModuleService.getTaxLines(
- [
- ...(cart.items as TaxableItemDTO[]),
- ...(cart.shipping_methods as TaxableShippingDTO[]),
- ],
- {
- address: {
- ...cart.shipping_address,
- country_code:
- cart.shipping_address.country_code || "us",
- },
+ return new WorkflowResponse({ store })
}
)
```
-Then, use the returned tax lines to set the line items and shipping methods’ tax lines:
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-```ts
-// set line item tax lines
-await cartModuleService.setLineItemTaxLines(
- cart.id,
- taxLines.filter((line) => "line_item_id" in line)
-)
+### API Route
-// set shipping method tax lines
-await cartModuleService.setLineItemTaxLines(
- cart.id,
- taxLines.filter((line) => "shipping_line_id" in line)
-)
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createStoreWorkflow } from "../../workflows/create-store"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createStoreWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
```
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createStoreWorkflow } from "../workflows/create-store"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createStoreWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createStoreWorkflow } from "../workflows/create-store"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createStoreWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
# Links between Cart Module and Other Modules
@@ -20635,6 +20451,81 @@ const { data: carts } = useQueryGraphStep({
```
+# Tax Lines in Cart Module
+
+In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module.
+
+## What are Tax Lines?
+
+A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+
+
+
+***
+
+## Tax Inclusivity
+
+By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal.
+
+However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+
+So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+
+The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective.
+
+
+
+For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`.
+
+***
+
+## Retrieve Tax Lines
+
+When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
+
+```ts
+// retrieve the cart
+const cart = await cartModuleService.retrieveCart("cart_123", {
+ relations: [
+ "items.tax_lines",
+ "shipping_methods.tax_lines",
+ "shipping_address",
+ ],
+})
+
+// retrieve the tax lines
+const taxLines = await taxModuleService.getTaxLines(
+ [
+ ...(cart.items as TaxableItemDTO[]),
+ ...(cart.shipping_methods as TaxableShippingDTO[]),
+ ],
+ {
+ address: {
+ ...cart.shipping_address,
+ country_code:
+ cart.shipping_address.country_code || "us",
+ },
+ }
+)
+```
+
+Then, use the returned tax lines to set the line items and shipping methods’ tax lines:
+
+```ts
+// set line item tax lines
+await cartModuleService.setLineItemTaxLines(
+ cart.id,
+ taxLines.filter((line) => "line_item_id" in line)
+)
+
+// set shipping method tax lines
+await cartModuleService.setLineItemTaxLines(
+ cart.id,
+ taxLines.filter((line) => "shipping_line_id" in line)
+)
+```
+
+
# Promotions Adjustments in Carts
In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
@@ -20753,858 +20644,6 @@ await cartModuleService.setShippingMethodAdjustments(
```
-# Fulfillment Concepts
-
-In this document, you’ll learn about some basic fulfillment concepts.
-
-## Fulfillment Set
-
-A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets.
-
-A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another.
-
-```ts
-const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets(
- [
- {
- name: "Shipping",
- type: "shipping",
- },
- {
- name: "Pick-up",
- type: "pick-up",
- },
- ]
-)
-```
-
-***
-
-## Service Zone
-
-A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations.
-
-A service zone is represented by the [ServiceZone data model](https://docs.medusajs.com/references/fulfillment/models/ServiceZone/index.html.md). It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location.
-
-
-
-A service zone can have multiple geographical zones, each represented by the [GeoZone data model](https://docs.medusajs.com/references/fulfillment/models/GeoZone/index.html.md). It holds location-related details to narrow down supported areas, such as country, city, or province code.
-
-***
-
-## Shipping Profile
-
-A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally.
-
-A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
-
-
-# Fulfillment Module Provider
-
-In this document, you’ll learn what a fulfillment module provider is.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/locations#manage-fulfillment-providers/index.html.md) to learn how to add a fulfillment provider to a location using the dashboard.
-
-## What’s a Fulfillment Module Provider?
-
-A fulfillment module provider handles fulfilling items, typically using a third-party integration.
-
-Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
-
-***
-
-## Configure Fulfillment Providers
-
-The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
-
-***
-
-## How to Create a Fulfillment Provider?
-
-Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
-
-
-# Item Fulfillment
-
-In this document, you’ll learn about the concepts of item fulfillment.
-
-## Fulfillment Data Model
-
-A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
-
-***
-
-## Fulfillment Processing by a Fulfillment Provider
-
-A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
-
-The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
-
-
-
-***
-
-## data Property
-
-The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
-
-For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
-
-***
-
-## Fulfillment Items
-
-A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
-
-The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
-
-
-
-***
-
-## Fulfillment Label
-
-Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
-
-***
-
-## Fulfillment Status
-
-The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
-
-- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
-- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
-- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
-
-
-# Links between Fulfillment Module and Other Modules
-
-This document showcases the module links defined between the Fulfillment Module and other commerce modules.
-
-## Summary
-
-The Fulfillment Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Order Module
-
-The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
-
-Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
-
-
-
-A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-To retrieve the return, pass `return.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: fulfillments } = await query.graph({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
-
-// fulfillments.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: fulfillments } = useQueryGraphStep({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
-
-// fulfillments.order
-```
-
-### Manage with Link
-
-To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
-
-***
-
-## Pricing Module
-
-The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context.
-
-Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
-
-
-
-### Retrieve with Query
-
-To retrieve the price set of a shipping option with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: shippingOptions } = await query.graph({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
-
-// shippingOptions.price_set
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: shippingOptions } = useQueryGraphStep({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
-
-// shippingOptions.price_set
-```
-
-### Manage with Link
-
-To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Product Module
-
-Medusa defines a link between the `ShippingProfile` data model and the `Product` data model of the Product Module. Each product must belong to a shipping profile.
-
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
-
-### Retrieve with Query
-
-To retrieve the products of a shipping profile with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: shippingProfiles } = await query.graph({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
-
-// shippingProfiles.products
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: shippingProfiles } = useQueryGraphStep({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
-
-// shippingProfiles.products
-```
-
-### Manage with Link
-
-To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-***
-
-## Stock Location Module
-
-The Stock Location Module provides features to manage stock locations in a store.
-
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location.
-
-
-
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
-
-
-
-### Retrieve with Query
-
-To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`:
-
-To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: fulfillmentSets } = await query.graph({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
-
-// fulfillmentSets.location
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: fulfillmentSets } = useQueryGraphStep({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
-
-// fulfillmentSets.location
-```
-
-### Manage with Link
-
-To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-
-# Fulfillment Module Options
-
-In this document, you'll learn about the options of the Fulfillment Module.
-
-## providers
-
-The `providers` option is an array of fulfillment module providers.
-
-When the Medusa application starts, these providers are registered and can be used to process fulfillments.
-
-For example:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/fulfillment",
- options: {
- providers: [
- {
- resolve: `@medusajs/medusa/fulfillment-manual`,
- id: "manual",
- options: {
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating either the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Shipping Option
-
-In this document, you’ll learn about shipping options and their rules.
-
-## What’s a Shipping Option?
-
-A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping.
-
-When the customer places their order, they choose a shipping option to be used to fulfill their items.
-
-A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md).
-
-***
-
-## Service Zone Restrictions
-
-A shipping option is restricted by a service zone, limiting the locations a shipping option be used in.
-
-For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada.
-
-
-
-Service zones can be more restrictive, such as restricting to certain cities or province codes.
-
-
-
-***
-
-## Shipping Option Rules
-
-You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
-
-These rules are represented by the [ShippingOptionRule data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionRule/index.html.md). Its properties define the custom rule:
-
-- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`.
-- `operator`: The operator used in the condition. For example:
- - To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values.
- - To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values.
- - Check out more operators in [this reference](https://docs.medusajs.com/references/fulfillment/types/fulfillment.RuleOperatorType/index.html.md).
-- `value`: One or more values.
-
-
-
-A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g.
-
-
-
-***
-
-## Shipping Profile and Types
-
-A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md).
-
-A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner.
-
-***
-
-## data Property
-
-When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process.
-
-The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment.
-
-
-# Links between Currency Module and Other Modules
-
-This document showcases the module links defined between the Currency Module and other commerce modules.
-
-## Summary
-
-The Currency Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Read-only||
-
-***
-
-## Store Module
-
-The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
-
-Instead, Medusa defines a read-only link between the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model and the Currency Module's `Currency` data model. Because the link is read-only from the `Store`'s side, you can only retrieve the details of a store's supported currencies, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stores } = await query.graph({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-
-# Customer Accounts
-
-In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers using the dashboard.
-
-## `has_account` Property
-
-The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
-
-When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
-
-When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
-
-***
-
-## Email Uniqueness
-
-The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
-
-So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
-
-
-# Links between Customer Module and Other Modules
-
-This document showcases the module links defined between the Customer Module and other commerce modules.
-
-## Summary
-
-The Customer Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-|| in |Stored||
-| in ||Read-only||
-| in ||Read-only||
-
-***
-
-## Payment Module
-
-Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
-
-This link is available starting from Medusa `v2.5.0`.
-
-### Retrieve with Query
-
-To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: customers } = await query.graph({
- entity: "customer",
- fields: [
- "account_holders.*",
- ],
-})
-
-// customers.account_holders
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: [
- "account_holders.*",
- ],
-})
-
-// customers.account_holders
-```
-
-### Manage with Link
-
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-***
-
-## Cart Module
-
-Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `Customer` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the customer of a cart, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "customer.*",
- ],
-})
-
-// carts.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "customer.*",
- ],
-})
-
-// carts.customer
-```
-
-***
-
-## Order Module
-
-Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `Customer` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the customer of an order, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "customer.*",
- ],
-})
-
-// orders.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "customer.*",
- ],
-})
-
-// orders.customer
-```
-
-
# Authentication Flows with the Auth Main Service
In this document, you'll learn how to use the Auth Module's main service's methods to implement authentication flows and reset a user's password.
@@ -21807,54 +20846,6 @@ In the example above, you use the `emailpass` provider, so you have to pass an o
If the returned `success` property is `true`, the password has reset successfully.
-# Auth Providers
-
-In this document, you’ll learn how the Auth Module handles authentication using providers.
-
-## What's an Auth Module Provider?
-
-An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
-
-For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account.
-
-### Auth Providers List
-
-- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md)
-- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md)
-- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md)
-
-***
-
-## Configure Allowed Auth Providers of Actor Types
-
-By default, users of all actor types can authenticate with all installed auth module providers.
-
-To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/docs/learn/configurations/medusa-config#httpauthMethodsPerActor/index.html.md) in Medusa's configurations:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- http: {
- authMethodsPerActor: {
- user: ["google"],
- customer: ["emailpass"],
- },
- // ...
- },
- // ...
- },
-})
-```
-
-When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider.
-
-***
-
-## How to Create an Auth Module Provider
-
-Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider.
-
-
# Auth Identity and Actor Types
In this document, you’ll learn about concepts related to identity and actors in the Auth Module.
@@ -21924,350 +20915,6 @@ For example, if you have a custom module with a `Manager` data model, you can au
Learn how to create a custom actor type in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md).
-# How to Use Authentication Routes
-
-In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password.
-
-These routes are added by Medusa's HTTP layer, not the Auth Module.
-
-## Types of Authentication Flows
-
-### 1. Basic Authentication Flow
-
-This authentication flow doesn't require validation with third-party services.
-
-[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md).
-
-The steps are:
-
-
-
-1. Register the user with the [Register Route](#register-route).
-2. Use the authentication token to create the user with their respective API route.
- - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
- - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
-3. Authenticate the user with the [Auth Route](#login-route).
-
-After registration, you only use the [Auth Route](#login-route) for subsequent authentication.
-
-To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
-
-### 2. Third-Party Service Authenticate Flow
-
-This authentication flow authenticates the user with a third-party service, such as Google.
-
-[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
-
-It requires the following steps:
-
-
-
-1. Authenticate the user with the [Auth Route](#login-route).
-2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location.
-3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication.
-4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters.
-5. If the callback validation is successful, the frontend receives the authentication token.
-6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
- - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
- - If not, follow the rest of the steps.
-7. The frontend uses the authentication token to create the user with their respective API route.
- - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
- - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
-8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated.
-
-***
-
-## Register Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com"
- // ...
-}'
-```
-
-This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead.
-
-For example, if you're registering a customer, you:
-
-1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token.
-2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication).
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-### Request Body Parameters
-
-This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
-
-For example, the EmailPass provider requires an `email` and `password` fields in the request body.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-### Handling Existing Identities
-
-An auth identity with the same email may already exist in Medusa. This can happen if:
-
-- Another actor type is using that email. For example, an admin user is trying to register as a customer.
-- The same email belongs to a record of the same actor type. For example, another customer has the same email.
-
-In these scenarios, the Register Route will return an error instead of a token:
-
-```json
-{
- "type": "unauthorized",
- "message": "Identity with email already exists"
-}
-```
-
-To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer.
-
-Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error:
-
-```json
-{
- "type": "unauthorized",
- "message": "Invalid email or password"
-}
-```
-
-You can show that error message to the customer.
-
-***
-
-## Login Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com"
- // ...
-}'
-```
-
-For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`.
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-### Request Body Parameters
-
-This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
-
-For example, the EmailPass provider requires an `email` and `password` fields in the request body.
-
-#### Overriding Callback URL
-
-For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations.
-
-This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-If the authentication requires more action with a third-party service, you'll receive a `location` property:
-
-```json
-{
- "location": "https://..."
-}
-```
-
-Redirect to that URL in the frontend to continue the authentication process with the third-party service.
-
-[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md).
-
-***
-
-## Validate Callback Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456
-```
-
-Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow.
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `google`.
-
-### Query Parameters
-
-This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt):
-
-- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests.
-- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
-
-***
-
-## Refresh Token Route
-
-The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information.
-
-It requires the user's JWT token that they received from the authentication or callback routes.
-
-```bash
-curl -X POST http://localhost:9000/auth/token/refresh \
--H 'Authorization: Bearer {token}'
-```
-
-### Response Fields
-
-If the token was refreshed successfully, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-***
-
-## Reset Password Routes
-
-To reset a user's password:
-
-1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route).
- - The API route emits the `auth.password_reset` event, passing the token in the payload.
- - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user.
-2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password.
- - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route.
-
-[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
-
-### Generate Reset Password Token Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password
--H 'Content-Type: application/json' \
---data-raw '{
- "identifier": "Whitney_Schultz@gmail.com"
-}'
-```
-
-This API route is useful for providers like `emailpass` that store a user's password and use it for authentication.
-
-#### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-#### Request Body Parameters
-
-This route accepts in the request body an object having the following property:
-
-- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email.
-
-#### Response Fields
-
-If the authentication is successful, the request returns a `201` response code.
-
-### Reset Password Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {token}' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com",
- "password": "supersecret"
-}'
-```
-
-This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in.
-
-#### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-#### Pass Token in Authorization Header
-
-Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization` header.
-
-In the request's authorization header, you must pass the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route). You pass it as a bearer token.
-
-### Request Body Parameters
-
-This route accepts in the request body an object that has the data necessary for the provider to update the user's password.
-
-For the `emailpass` provider, you must pass the following properties:
-
-- `email`: The user's email.
-- `password`: The new password.
-
-### Response Fields
-
-If the authentication is successful, the request returns an object with a `success` property set to `true`:
-
-```json
-{
- "success": "true"
-}
-```
-
-
# How to Create an Actor Type
In this document, learn how to create an actor type and authenticate its associated data model.
@@ -22661,6 +21308,398 @@ In the workflow, you:
You can use this workflow when deleting a manager, such as in an API route.
+# How to Use Authentication Routes
+
+In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password.
+
+These routes are added by Medusa's HTTP layer, not the Auth Module.
+
+## Types of Authentication Flows
+
+### 1. Basic Authentication Flow
+
+This authentication flow doesn't require validation with third-party services.
+
+[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md).
+
+The steps are:
+
+
+
+1. Register the user with the [Register Route](#register-route).
+2. Use the authentication token to create the user with their respective API route.
+ - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+ - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
+3. Authenticate the user with the [Auth Route](#login-route).
+
+After registration, you only use the [Auth Route](#login-route) for subsequent authentication.
+
+To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
+
+### 2. Third-Party Service Authenticate Flow
+
+This authentication flow authenticates the user with a third-party service, such as Google.
+
+[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+
+It requires the following steps:
+
+
+
+1. Authenticate the user with the [Auth Route](#login-route).
+2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location.
+3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication.
+4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters.
+5. If the callback validation is successful, the frontend receives the authentication token.
+6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
+ - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
+ - If not, follow the rest of the steps.
+7. The frontend uses the authentication token to create the user with their respective API route.
+ - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+ - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
+8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated.
+
+***
+
+## Register Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com"
+ // ...
+}'
+```
+
+This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead.
+
+For example, if you're registering a customer, you:
+
+1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token.
+2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication).
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+### Request Body Parameters
+
+This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
+
+For example, the EmailPass provider requires an `email` and `password` fields in the request body.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+### Handling Existing Identities
+
+An auth identity with the same email may already exist in Medusa. This can happen if:
+
+- Another actor type is using that email. For example, an admin user is trying to register as a customer.
+- The same email belongs to a record of the same actor type. For example, another customer has the same email.
+
+In these scenarios, the Register Route will return an error instead of a token:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Identity with email already exists"
+}
+```
+
+To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer.
+
+Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Invalid email or password"
+}
+```
+
+You can show that error message to the customer.
+
+***
+
+## Login Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com"
+ // ...
+}'
+```
+
+For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`.
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+### Request Body Parameters
+
+This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
+
+For example, the EmailPass provider requires an `email` and `password` fields in the request body.
+
+#### Overriding Callback URL
+
+For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations.
+
+This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+If the authentication requires more action with a third-party service, you'll receive a `location` property:
+
+```json
+{
+ "location": "https://..."
+}
+```
+
+Redirect to that URL in the frontend to continue the authentication process with the third-party service.
+
+[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md).
+
+***
+
+## Validate Callback Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456
+```
+
+Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow.
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `google`.
+
+### Query Parameters
+
+This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt):
+
+- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests.
+- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+
+***
+
+## Refresh Token Route
+
+The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information.
+
+It requires the user's JWT token that they received from the authentication or callback routes.
+
+```bash
+curl -X POST http://localhost:9000/auth/token/refresh \
+-H 'Authorization: Bearer {token}'
+```
+
+### Response Fields
+
+If the token was refreshed successfully, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+***
+
+## Reset Password Routes
+
+To reset a user's password:
+
+1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route).
+ - The API route emits the `auth.password_reset` event, passing the token in the payload.
+ - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user.
+2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password.
+ - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route.
+
+[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
+
+### Generate Reset Password Token Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "identifier": "Whitney_Schultz@gmail.com"
+}'
+```
+
+This API route is useful for providers like `emailpass` that store a user's password and use it for authentication.
+
+#### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+#### Request Body Parameters
+
+This route accepts in the request body an object having the following property:
+
+- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email.
+
+#### Response Fields
+
+If the authentication is successful, the request returns a `201` response code.
+
+### Reset Password Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com",
+ "password": "supersecret"
+}'
+```
+
+This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in.
+
+#### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+#### Pass Token in Authorization Header
+
+Before [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6), you passed the token as a query parameter. Now, you must pass it in the `Authorization` header.
+
+In the request's authorization header, you must pass the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route). You pass it as a bearer token.
+
+### Request Body Parameters
+
+This route accepts in the request body an object that has the data necessary for the provider to update the user's password.
+
+For the `emailpass` provider, you must pass the following properties:
+
+- `email`: The user's email.
+- `password`: The new password.
+
+### Response Fields
+
+If the authentication is successful, the request returns an object with a `success` property set to `true`:
+
+```json
+{
+ "success": "true"
+}
+```
+
+
+# Auth Providers
+
+In this document, you’ll learn how the Auth Module handles authentication using providers.
+
+## What's an Auth Module Provider?
+
+An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
+
+For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account.
+
+### Auth Providers List
+
+- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md)
+- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md)
+- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md)
+
+***
+
+## Configure Allowed Auth Providers of Actor Types
+
+By default, users of all actor types can authenticate with all installed auth module providers.
+
+To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/docs/learn/configurations/medusa-config#httpauthMethodsPerActor/index.html.md) in Medusa's configurations:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ http: {
+ authMethodsPerActor: {
+ user: ["google"],
+ customer: ["emailpass"],
+ },
+ // ...
+ },
+ // ...
+ },
+})
+```
+
+When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider.
+
+***
+
+## How to Create an Auth Module Provider
+
+Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider.
+
+
# Auth Module Options
In this document, you'll learn about the options of the Auth Module.
@@ -22841,6 +21880,882 @@ The page shows the user password fields to enter their new password, then submit
- [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
+# Links between API Key Module and Other Modules
+
+This document showcases the module links defined between the API Key Module and other commerce modules.
+
+## Summary
+
+The API Key Module has the following links to other modules:
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+|| in |Stored||
+
+***
+
+## Sales Channel Module
+
+You can create a publishable API key and associate it with a sales channel. Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
+
+
+
+This is useful to avoid passing the sales channel's ID as a parameter of every request, and instead pass the publishable API key in the header of any request to the Store API route.
+
+Learn more about this in the [Sales Channel Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the sales channels of an API key with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: apiKeys } = await query.graph({
+ entity: "api_key",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// apiKeys.sales_channels
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: apiKeys } = useQueryGraphStep({
+ entity: "api_key",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// apiKeys.sales_channels
+```
+
+### Manage with Link
+
+To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+
+# API Key Concepts
+
+In this document, you’ll learn about the different types of API keys, their expiration and verification.
+
+## API Key Types
+
+There are two types of API keys:
+
+- `publishable`: A public key used in client applications, such as a storefront.
+- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
+
+The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
+
+***
+
+## API Key Expiration
+
+An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md).
+
+The associated token is no longer usable or verifiable.
+
+***
+
+## Token Verification
+
+To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
+
+
+# Customer Accounts
+
+In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/customers/index.html.md) to learn how to manage customers using the dashboard.
+
+## `has_account` Property
+
+The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
+
+When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
+
+When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
+
+***
+
+## Email Uniqueness
+
+The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
+
+So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
+
+
+# Links between Customer Module and Other Modules
+
+This document showcases the module links defined between the Customer Module and other commerce modules.
+
+## Summary
+
+The Customer Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+|| in |Stored||
+| in ||Read-only||
+| in ||Read-only||
+
+***
+
+## Payment Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+This link is available starting from Medusa `v2.5.0`.
+
+### Retrieve with Query
+
+To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: customers } = await query.graph({
+ entity: "customer",
+ fields: [
+ "account_holders.*",
+ ],
+})
+
+// customers.account_holders
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: [
+ "account_holders.*",
+ ],
+})
+
+// customers.account_holders
+```
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+***
+
+## Cart Module
+
+Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `Customer` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the customer of a cart, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// carts.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// carts.customer
+```
+
+***
+
+## Order Module
+
+Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `Customer` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the customer of an order, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// orders.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// orders.customer
+```
+
+
+# Fulfillment Module Provider
+
+In this document, you’ll learn what a fulfillment module provider is.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/locations#manage-fulfillment-providers/index.html.md) to learn how to add a fulfillment provider to a location using the dashboard.
+
+## What’s a Fulfillment Module Provider?
+
+A fulfillment module provider handles fulfilling items, typically using a third-party integration.
+
+Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
+
+***
+
+## Configure Fulfillment Providers
+
+The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
+
+***
+
+## How to Create a Fulfillment Provider?
+
+Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
+
+
+# Item Fulfillment
+
+In this document, you’ll learn about the concepts of item fulfillment.
+
+## Fulfillment Data Model
+
+A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
+
+***
+
+## Fulfillment Processing by a Fulfillment Provider
+
+A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
+
+The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
+
+
+
+***
+
+## data Property
+
+The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
+
+For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
+
+***
+
+## Fulfillment Items
+
+A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
+
+The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
+
+
+
+***
+
+## Fulfillment Label
+
+Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
+
+***
+
+## Fulfillment Status
+
+The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
+
+- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
+- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
+- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
+
+
+# Shipping Option
+
+In this document, you’ll learn about shipping options and their rules.
+
+## What’s a Shipping Option?
+
+A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping.
+
+When the customer places their order, they choose a shipping option to be used to fulfill their items.
+
+A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md).
+
+***
+
+## Service Zone Restrictions
+
+A shipping option is restricted by a service zone, limiting the locations a shipping option be used in.
+
+For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada.
+
+
+
+Service zones can be more restrictive, such as restricting to certain cities or province codes.
+
+
+
+***
+
+## Shipping Option Rules
+
+You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
+
+These rules are represented by the [ShippingOptionRule data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionRule/index.html.md). Its properties define the custom rule:
+
+- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`.
+- `operator`: The operator used in the condition. For example:
+ - To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values.
+ - To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values.
+ - Check out more operators in [this reference](https://docs.medusajs.com/references/fulfillment/types/fulfillment.RuleOperatorType/index.html.md).
+- `value`: One or more values.
+
+
+
+A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g.
+
+
+
+***
+
+## Shipping Profile and Types
+
+A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md).
+
+A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner.
+
+***
+
+## data Property
+
+When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process.
+
+The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment.
+
+
+# Links between Fulfillment Module and Other Modules
+
+This document showcases the module links defined between the Fulfillment Module and other commerce modules.
+
+## Summary
+
+The Fulfillment Module has the following links to other modules:
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Order Module
+
+The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
+
+Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
+
+
+
+A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+To retrieve the return, pass `return.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: fulfillments } = await query.graph({
+ entity: "fulfillment",
+ fields: [
+ "order.*",
+ ],
+})
+
+// fulfillments.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: fulfillments } = useQueryGraphStep({
+ entity: "fulfillment",
+ fields: [
+ "order.*",
+ ],
+})
+
+// fulfillments.order
+```
+
+### Manage with Link
+
+To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
+```
+
+***
+
+## Pricing Module
+
+The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context.
+
+Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
+
+
+
+### Retrieve with Query
+
+To retrieve the price set of a shipping option with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: shippingOptions } = await query.graph({
+ entity: "shipping_option",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// shippingOptions.price_set
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: shippingOptions } = useQueryGraphStep({
+ entity: "shipping_option",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// shippingOptions.price_set
+```
+
+### Manage with Link
+
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+***
+
+## Product Module
+
+Medusa defines a link between the `ShippingProfile` data model and the `Product` data model of the Product Module. Each product must belong to a shipping profile.
+
+This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
+
+### Retrieve with Query
+
+To retrieve the products of a shipping profile with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: shippingProfiles } = await query.graph({
+ entity: "shipping_profile",
+ fields: [
+ "products.*",
+ ],
+})
+
+// shippingProfiles.products
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: shippingProfiles } = useQueryGraphStep({
+ entity: "shipping_profile",
+ fields: [
+ "products.*",
+ ],
+})
+
+// shippingProfiles.products
+```
+
+### Manage with Link
+
+To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+***
+
+## Stock Location Module
+
+The Stock Location Module provides features to manage stock locations in a store.
+
+Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location.
+
+
+
+Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
+
+
+
+### Retrieve with Query
+
+To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`:
+
+To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: fulfillmentSets } = await query.graph({
+ entity: "fulfillment_set",
+ fields: [
+ "location.*",
+ ],
+})
+
+// fulfillmentSets.location
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: fulfillmentSets } = useQueryGraphStep({
+ entity: "fulfillment_set",
+ fields: [
+ "location.*",
+ ],
+})
+
+// fulfillmentSets.location
+```
+
+### Manage with Link
+
+To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+
+# Fulfillment Concepts
+
+In this document, you’ll learn about some basic fulfillment concepts.
+
+## Fulfillment Set
+
+A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets.
+
+A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another.
+
+```ts
+const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets(
+ [
+ {
+ name: "Shipping",
+ type: "shipping",
+ },
+ {
+ name: "Pick-up",
+ type: "pick-up",
+ },
+ ]
+)
+```
+
+***
+
+## Service Zone
+
+A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations.
+
+A service zone is represented by the [ServiceZone data model](https://docs.medusajs.com/references/fulfillment/models/ServiceZone/index.html.md). It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location.
+
+
+
+A service zone can have multiple geographical zones, each represented by the [GeoZone data model](https://docs.medusajs.com/references/fulfillment/models/GeoZone/index.html.md). It holds location-related details to narrow down supported areas, such as country, city, or province code.
+
+***
+
+## Shipping Profile
+
+A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally.
+
+A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
+
+
# Inventory Concepts
In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
@@ -22884,6 +22799,51 @@ A reservation item, represented by the [ReservationItem](https://docs.medusajs.c
The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module.
+# Fulfillment Module Options
+
+In this document, you'll learn about the options of the Fulfillment Module.
+
+## providers
+
+The `providers` option is an array of fulfillment module providers.
+
+When the Medusa application starts, these providers are registered and can be used to process fulfillments.
+
+For example:
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/fulfillment",
+ options: {
+ providers: [
+ {
+ resolve: `@medusajs/medusa/fulfillment-manual`,
+ id: "manual",
+ options: {
+ // provider options...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The `providers` option is an array of objects that accept the following properties:
+
+- `resolve`: A string indicating either the package name of the module provider or the path to it relative to the `src` directory.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+
# Inventory Module in Medusa Flows
This document explains how the Inventory Module is used within the Medusa application's flows.
@@ -22945,147 +22905,6 @@ This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.
If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level.
-# Links between Inventory Module and Other Modules
-
-This document showcases the module links defined between the Inventory Module and other commerce modules.
-
-## Summary
-
-The Inventory Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-|| in |Read-only||
-
-***
-
-## Product Module
-
-Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models.
-
-
-
-A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
-
-Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: [
- "variants.*",
- ],
-})
-
-// inventoryItems.variants
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryItems } = useQueryGraphStep({
- entity: "inventory_item",
- fields: [
- "variants.*",
- ],
-})
-
-// inventoryItems.variants
-```
-
-### Manage with Link
-
-To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-***
-
-## Stock Location Module
-
-Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model.
-
-### Retrieve with Query
-
-To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryLevels } = await query.graph({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryLevels } = useQueryGraphStep({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-
# Inventory Kits
In this guide, you'll learn how inventory kits can be used in the Medusa application to support use cases like multi-part products, bundled products, and shared inventory across products.
@@ -23474,1931 +23293,6 @@ The bundled product has the same inventory items as those of the products part o
You can now [execute the workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows#3-execute-the-workflow/index.html.md) in [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [scheduled jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md), or [subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md).
-# Pricing Concepts
-
-In this document, you’ll learn about the main concepts in the Pricing Module.
-
-## Price Set
-
-A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option).
-
-Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
-
-
-
-***
-
-## Price List
-
-A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied.
-
-A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
-
-Its associated prices are represented by the `Price` data model.
-
-
-# Links between Pricing Module and Other Modules
-
-This document showcases the module links defined between the Pricing Module and other commerce modules.
-
-## Summary
-
-The Pricing Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Fulfillment Module
-
-The Fulfillment Module provides fulfillment-related functionalities, including shipping options that the customer chooses from when they place their order. However, it doesn't provide pricing-related functionalities for these options.
-
-Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
-
-
-
-### Retrieve with Query
-
-To retrieve the shipping option of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_option.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: priceSets } = await query.graph({
- entity: "price_set",
- fields: [
- "shipping_option.*",
- ],
-})
-
-// priceSets.shipping_option
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
- fields: [
- "shipping_option.*",
- ],
-})
-
-// priceSets.shipping_option
-```
-
-### Manage with Link
-
-To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Product Module
-
-The Product Module doesn't store or manage the prices of product variants.
-
-Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
-
-
-
-So, when you want to add prices for a product variant, you create a price set and add the prices to it.
-
-You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
-
-### Retrieve with Query
-
-To retrieve the variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: priceSets } = await query.graph({
- entity: "price_set",
- fields: [
- "variant.*",
- ],
-})
-
-// priceSets.variant
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
- fields: [
- "variant.*",
- ],
-})
-
-// priceSets.variant
-```
-
-### Manage with Link
-
-To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-
-# Prices Calculation
-
-In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
-
-## calculatePrices Method
-
-The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
-
-It returns a price object with the best matching price for each price set.
-
-### Calculation Context
-
-The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
-
-For example:
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSetId] },
- {
- context: {
- currency_code: currencyCode,
- region_id: "reg_123",
- },
- }
-)
-```
-
-In this example, you retrieve the prices in a price set for the specified currency code and region ID.
-
-### Returned Price Object
-
-For each price set, the `calculatePrices` method selects two prices:
-
-- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
-- An original price, which is either:
- - The same price as the calculated price if the price list it belongs to is of type `override`;
- - Or a price that doesn't belong to a price list and best matches the specified context.
-
-Both prices are returned in an object that has the following properties:
-
-- id: (\`string\`) The ID of the price set from which the price was selected.
-- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
-- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
-- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
-- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
-- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
-- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- calculated\_price: (\`object\`) The calculated price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-- original\_price: (\`object\`) The original price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-
-***
-
-## Examples
-
-Consider the following price set:
-
-```ts
-const priceSet = await pricingModuleService.createPriceSets({
- prices: [
- // default price
- {
- amount: 500,
- currency_code: "EUR",
- rules: {},
- },
- // prices with rules
- {
- amount: 400,
- currency_code: "EUR",
- rules: {
- region_id: "reg_123",
- },
- },
- {
- amount: 450,
- currency_code: "EUR",
- rules: {
- city: "krakow",
- },
- },
- {
- amount: 500,
- currency_code: "EUR",
- rules: {
- city: "warsaw",
- region_id: "reg_123",
- },
- },
- ],
-})
-```
-
-### Default Price Selection
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR"
- }
- }
-)
-```
-
-### Result
-
-### Calculate Prices with Rules
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "reg_123",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-### Price Selection with Price List
-
-### Code
-
-```ts
-const priceList = pricingModuleService.createPriceLists([{
- title: "Summer Price List",
- description: "Price list for summer sale",
- starts_at: Date.parse("01/10/2023").toString(),
- ends_at: Date.parse("31/10/2023").toString(),
- rules: {
- region_id: ['PL']
- },
- type: "sale",
- prices: [
- {
- amount: 400,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- {
- amount: 450,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- ],
-}]);
-
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "PL",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-
-# Price Rules
-
-In this document, you'll learn about price rules for price sets and price lists.
-
-## Price Rule
-
-You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md).
-
-The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
-
-For exmaple, you create a price restricted to `10557` zip codes.
-
-
-
-A price can have multiple price rules.
-
-For example, a price can be restricted by a region and a zip code.
-
-
-
-***
-
-## Price List Rules
-
-Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
-
-The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
-
-
-
-
-# Tax-Inclusive Pricing
-
-In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
-
-## What is Tax-Inclusive Pricing?
-
-A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
-
-For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
-
-***
-
-## How is Tax-Inclusive Pricing Set?
-
-The [PricePreference data model](https://docs.medusajs.com/references/pricing/models/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
-
-- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
-- `value`: The attribute’s value. For example, `reg_123` or `usd`.
-
-Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
-
-The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
-
-For example:
-
-```json
-{
- "attribute": "currency_code",
- "value": "USD",
- "is_tax_inclusive": true,
-}
-```
-
-In this example, tax-inclusivity is enabled for the `USD` currency code.
-
-***
-
-## Tax-Inclusive Pricing in Price Calculation
-
-### Tax Context
-
-As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
-
-To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
-
-### Returned Tax Properties
-
-The `calculatePrices` method returns two properties related to tax-inclusivity:
-
-Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
-
-- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
-- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
-
-A price is considered tax-inclusive if:
-
-1. It belongs to the region or currency code specified in the calculation context;
-2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
-
-### Tax Context Precedence
-
-A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
-
-- both the `region_id` and `currency_code` are provided in the calculation context;
-- the selected price belongs to the region;
-- and the region has a price preference
-
-
-# Links between Product Module and Other Modules
-
-This document showcases the module links defined between the Product Module and other commerce modules.
-
-## Summary
-
-The Product Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Read-only||
-|| in |Stored||
-|| in |Stored||
-| in ||Read-only||
-|| in |Stored||
-|| in |Stored||
-
-***
-
-## Cart Module
-
-Medusa defines read-only links between:
-
-- The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model and the `Product` data model. Because the link is read-only from the `LineItem`'s side, you can only retrieve the product of a line item, and not the other way around.
-- The `ProductVariant` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. Because the link is read-only from the `LineItem`'s side, you can only retrieve the variant of a line item, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-To retrieve the product, pass `product.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: lineItems } = await query.graph({
- entity: "line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: lineItems } = useQueryGraphStep({
- entity: "line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-***
-
-## Fulfillment Module
-
-Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile.
-
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
-
-### Retrieve with Query
-
-To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "shipping_profile.*",
- ],
-})
-
-// products.shipping_profile
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: [
- "shipping_profile.*",
- ],
-})
-
-// products.shipping_profile
-```
-
-### Manage with Link
-
-To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-***
-
-## Inventory Module
-
-The Inventory Module provides inventory-management features for any stock-kept item.
-
-Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details.
-
-
-
-When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
-
-Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the inventory items of a product variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_items.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: variants } = await query.graph({
- entity: "variant",
- fields: [
- "inventory_items.*",
- ],
-})
-
-// variants.inventory_items
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "inventory_items.*",
- ],
-})
-
-// variants.inventory_items
-```
-
-### Manage with Link
-
-To manage the inventory items of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-Medusa defines read-only links between:
-
-- the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model and the `Product` data model. Because the link is read-only from the `OrderLineItem`'s side, you can only retrieve the product of an order line item, and not the other way around.
-- the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model and the `ProductVariant` data model. Because the link is read-only from the `OrderLineItem`'s side, you can only retrieve the variant of an order line item, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
-
-To retrieve the product, pass `product.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: lineItems } = await query.graph({
- entity: "order_line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: lineItems } = useQueryGraphStep({
- entity: "order_line_item",
- fields: [
- "variant.*",
- ],
-})
-
-// lineItems.variant
-```
-
-***
-
-## Pricing Module
-
-The Product Module doesn't provide pricing-related features.
-
-Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set.
-
-
-
-So, to add prices for a product variant, create a price set and add the prices to it.
-
-### Retrieve with Query
-
-To retrieve the price set of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: variants } = await query.graph({
- entity: "variant",
- fields: [
- "price_set.*",
- ],
-})
-
-// variants.price_set
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "price_set.*",
- ],
-})
-
-// variants.price_set
-```
-
-### Manage with Link
-
-To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Sales Channel Module
-
-The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
-
-Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
-
-
-
-### Retrieve with Query
-
-To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// products.sales_channels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// products.sales_channels
-```
-
-### Manage with Link
-
-To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-
-# Product Variant Inventory
-
-# Product Variant Inventory
-
-In this guide, you'll learn about the inventory management features related to product variants.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/variants#manage-product-variant-inventory/index.html.md) to learn how to manage inventory of product variants.
-
-## Configure Inventory Management of Product Variants
-
-A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
-
-The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
-
-When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
-
-***
-
-## How the Medusa Application Manages Inventory
-
-When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
-
-
-
-The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
-
-
-
-Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
-
-The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
-
-
-
-Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
-
-### Product Inventory in Storefronts
-
-When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
-
-The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
-
-For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
-
-
-
-***
-
-## Variant Back Orders
-
-Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
-
-You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
-
-***
-
-## Additional Resources
-
-The following guides provide more details on inventory management in the Medusa application:
-
-- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
-- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
-- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
-- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
-
-
-# Configure Selling Products
-
-In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
-
-The concepts in this guide are applicable starting from Medusa v2.5.1.
-
-## Scenario
-
-Businesses can have different selling requirements:
-
-1. They may sell physical or digital items.
-2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
-3. They may sell items whose inventory is managed by an external system, such as an ERP.
-
-Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
-
-***
-
-## Configuring Shipping Requirements
-
-The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
-
-When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
-
-If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
-
-### Overriding Shipping Requirements for Variants
-
-A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
-
-Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
-
-1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
-2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
-
-***
-
-## Use Case Examples
-
-By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
-
-|Use Case|Configurations|Example|
-|---|---|---|---|---|
-|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
-|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
-|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
-|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
-
-
-# Account Holders and Saved Payment Methods
-
-In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
-
-Account holders are available starting from Medusa `v2.5.0`.
-
-## What's an Account Holder?
-
-An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
-
-It holds fields retrieved from the third-party provider, such as:
-
-- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
-- `data`: Data returned by the payment provider when the account holder is created.
-
-A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
-
-### Relation between Account Holder and Customer
-
-The Medusa application creates a link between the [Customer](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) data model of the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md) and the `AccountHolder` data model of the Payment Module.
-
-This link indicates that a customer can have more than one account holder, each representing saved payment methods in different payment providers.
-
-Learn more about this link in the [Link to Other Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/index.html.md) guide.
-
-***
-
-## Save Payment Methods
-
-If a payment provider supports saving payment methods for a customer, they must implement the following methods:
-
-- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
-- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
-- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
-- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
-
-Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
-
-***
-
-## Account Holder in Medusa Payment Flows
-
-In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
-
-Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
-
-This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
-
-
-# Links between Payment Module and Other Modules
-
-This document showcases the module links defined between the Payment Module and other commerce modules.
-
-## Summary
-
-The Payment Module has the following links to other modules:
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Cart Module
-
-The Cart Module provides cart-related features, but not payment processing.
-
-Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
-
-Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### Manage with Link
-
-To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Customer Module
-
-Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
-
-This link is available starting from Medusa `v2.5.0`.
-
-### Retrieve with Query
-
-To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: accountHolders } = await query.graph({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: accountHolders } = useQueryGraphStep({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### Manage with Link
-
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
-
-So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### Manage with Link
-
-To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Region Module
-
-You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
-
-
-
-This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
-
-### Retrieve with Query
-
-To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentProviders } = await query.graph({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentProviders } = useQueryGraphStep({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### Manage with Link
-
-To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-
-# Payment Module Options
-
-In this document, you'll learn about the options of the Payment Module.
-
-## All Module Options
-
-|Option|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
-|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
-|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
-
-***
-
-## providers Option
-
-The `providers` option is an array of payment module providers.
-
-When the Medusa application starts, these providers are registered and can be used to process payments.
-
-For example:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/payment",
- options: {
- providers: [
- {
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
- options: {
- // ...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Accept Payment Flow
-
-In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
-
-It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
-
-For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
-
-## Flow Overview
-
-
-
-***
-
-## 1. Create a Payment Collection
-
-A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-await createPaymentCollectionForCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: "cart_123",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentCollection =
- await paymentModuleService.createPaymentCollections({
- currency_code: "usd",
- amount: 5000,
- })
-```
-
-***
-
-## 2. Create Payment Sessions
-
-The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
-
-So, after creating the payment collection, create at least one payment session for a provider.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
- .run({
- input: {
- payment_collection_id: "paycol_123",
- provider_id: "stripe",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentSession =
- await paymentModuleService.createPaymentSession(
- paymentCollection.id,
- {
- provider_id: "stripe",
- currency_code: "usd",
- amount: 5000,
- data: {
- // any necessary data for the
- // payment provider
- },
- }
- )
-```
-
-***
-
-## 3. Authorize Payment Session
-
-Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
-
-For example:
-
-### Using Step
-
-```ts
-import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-### Using Service
-
-```ts
-const payment = authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-When the payment authorization is successful, a payment is created and returned.
-
-### Handling Additional Action
-
-If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
-
-If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
-
-In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
-
-For example:
-
-```ts
-try {
- const payment =
- await paymentModuleService.authorizePaymentSession(
- paymentSession.id,
- {}
- )
-} catch (e) {
- // retrieve the payment session again
- const updatedPaymentSession = (
- await paymentModuleService.listPaymentSessions({
- id: [paymentSession.id],
- })
- )[0]
-
- if (updatedPaymentSession.status === "requires_more") {
- // TODO perform required action
- // TODO authorize payment again.
- }
-}
-```
-
-***
-
-## 4. Payment Flow Complete
-
-The payment flow is complete once the payment session is authorized and the payment is created.
-
-You can then:
-
-- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
-- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
-
-Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
-
-
-# Payment Collection
-
-In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
-
-## What's a Payment Collection?
-
-A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
-
-Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
-
-- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
-- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
-- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
-
-***
-
-## Multiple Payments
-
-The payment collection supports multiple payment sessions and payments.
-
-You can use this to accept payments in increments or split payments across payment providers.
-
-
-
-***
-
-## Usage with the Cart Module
-
-The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
-
-During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
-
-It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
-
-
-
-
-# Webhook Events
-
-In this document, you’ll learn how the Payment Module supports listening to webhook events.
-
-## What's a Webhook Event?
-
-A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
-
-This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
-
-***
-
-## getWebhookActionAndData Method
-
-The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
-
-Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
-
-- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
-- `[provider]` is the ID of the provider. For example, `stripe`.
-
-For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
-
-Use that webhook listener in your third-party payment provider's configurations.
-
-
-
-If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
-
-If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
-
-### Actions After Webhook Payment Processing
-
-After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
-
-
-# Payment Module Provider
-
-In this document, you’ll learn what a payment module provider is.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/regions/index.html.md) to learn how to manage the payment providers available in a region using the dashboard.
-
-## What's a Payment Module Provider?
-
-A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
-
-To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
-
-After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
-
-### List of Payment Module Providers
-
-- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
-
-***
-
-## System Payment Provider
-
-The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
-
-It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
-
-***
-
-## How are Payment Providers Created?
-
-A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
-
-Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
-
-***
-
-## Configure Payment Providers
-
-The Payment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
-
-***
-
-## PaymentProvider Data Model
-
-When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
-
-This data model is used to reference a payment provider and determine whether it’s installed in the application.
-
-
-# Payment Session
-
-In this document, you’ll learn what a payment session is.
-
-## What's a Payment Session?
-
-A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
-
-A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
-
-
-
-***
-
-## data Property
-
-Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
-
-For example, the customer's ID in Stripe is stored in the `data` property.
-
-***
-
-## Payment Session Status
-
-The `status` property of a payment session indicates its current status. Its value can be:
-
-- `pending`: The payment session is awaiting authorization.
-- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
-- `authorized`: The payment session is authorized.
-- `error`: An error occurred while authorizing the payment.
-- `canceled`: The authorization of the payment session has been canceled.
-
-
-# Order Claim
-
-In this document, you’ll learn about order claims.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/claims/index.html.md) to learn how to manage an order's claims using the dashboard.
-
-## What is a Claim?
-
-When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
-
-The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim.
-
-***
-
-## Claim Type
-
-The `Claim` data model has a `type` property whose value indicates the type of the claim:
-
-- `refund`: the items are returned, and the customer is refunded.
-- `replace`: the items are returned, and the customer receives new items.
-
-***
-
-## Old and Replacement Items
-
-When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer.
-
-Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
-
-If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md).
-
-***
-
-## Claim Shipping Methods
-
-A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
-
-The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
-
-***
-
-## Claim Refund
-
-If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
-
-The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
-
-***
-
-## How Claims Impact an Order’s Version
-
-When a claim is confirmed, the order’s version is incremented.
-
-
-# Payment
-
-In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
-
-## What's a Payment?
-
-When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
-
-A payment carries many of the data and relations of a payment session:
-
-- It belongs to the same payment collection.
-- It’s associated with the same payment provider, which handles further payment processing.
-- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
-
-***
-
-## Capture Payments
-
-When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
-
-The payment can also be captured incrementally, each time a capture record is created for that amount.
-
-
-
-***
-
-## Refund Payments
-
-When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
-
-A payment can be refunded multiple times, and each time a refund record is created.
-
-
-
-
# Order Concepts
In this document, you’ll learn about orders and related concepts
@@ -25505,6 +23399,60 @@ Once the Order Edit is confirmed, any additional payment or refund required can
This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md).
+# Order Claim
+
+In this document, you’ll learn about order claims.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/claims/index.html.md) to learn how to manage an order's claims using the dashboard.
+
+## What is a Claim?
+
+When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
+
+The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim.
+
+***
+
+## Claim Type
+
+The `Claim` data model has a `type` property whose value indicates the type of the claim:
+
+- `refund`: the items are returned, and the customer is refunded.
+- `replace`: the items are returned, and the customer receives new items.
+
+***
+
+## Old and Replacement Items
+
+When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer.
+
+Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
+
+If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md).
+
+***
+
+## Claim Shipping Methods
+
+A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
+
+The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md).
+
+***
+
+## Claim Refund
+
+If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
+
+The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
+
+***
+
+## How Claims Impact an Order’s Version
+
+When a claim is confirmed, the order’s version is incremented.
+
+
# Order Exchange
In this document, you’ll learn about order exchanges.
@@ -26334,6 +24282,35 @@ await orderModuleService.setOrderShippingMethodAdjustments(
```
+# Tax Lines in Order Module
+
+In this document, you’ll learn about tax lines in an order.
+
+## What are Tax Lines?
+
+A tax line indicates the tax rate of a line item or a shipping method.
+
+The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+
+
+
+***
+
+## Tax Inclusivity
+
+By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal.
+
+However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+
+So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+
+The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective.
+
+
+
+For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`.
+
+
# Transactions
In this document, you’ll learn about an order’s transactions and its use.
@@ -26382,33 +24359,2914 @@ The `OrderTransaction` data model has two properties that determine which data m
- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
-# Tax Lines in Order Module
+# Links between Inventory Module and Other Modules
-In this document, you’ll learn about tax lines in an order.
+This document showcases the module links defined between the Inventory Module and other commerce modules.
-## What are Tax Lines?
+## Summary
-A tax line indicates the tax rate of a line item or a shipping method.
+The Inventory Module has the following links to other modules:
-The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line.
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+|| in |Read-only||
***
-## Tax Inclusivity
+## Product Module
-By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal.
+Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models.
-However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
+
-So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
+A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
-The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective.
+Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
+### Retrieve with Query
-For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`.
+To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
+ fields: [
+ "variants.*",
+ ],
+})
+
+// inventoryItems.variants
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: inventoryItems } = useQueryGraphStep({
+ entity: "inventory_item",
+ fields: [
+ "variants.*",
+ ],
+})
+
+// inventoryItems.variants
+```
+
+### Manage with Link
+
+To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+***
+
+## Stock Location Module
+
+Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model.
+
+### Retrieve with Query
+
+To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: inventoryLevels } = await query.graph({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: inventoryLevels } = useQueryGraphStep({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+
+# Account Holders and Saved Payment Methods
+
+In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+
+Account holders are available starting from Medusa `v2.5.0`.
+
+## What's an Account Holder?
+
+An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
+
+It holds fields retrieved from the third-party provider, such as:
+
+- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
+- `data`: Data returned by the payment provider when the account holder is created.
+
+A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
+
+### Relation between Account Holder and Customer
+
+The Medusa application creates a link between the [Customer](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) data model of the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md) and the `AccountHolder` data model of the Payment Module.
+
+This link indicates that a customer can have more than one account holder, each representing saved payment methods in different payment providers.
+
+Learn more about this link in the [Link to Other Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/links-to-other-modules/index.html.md) guide.
+
+***
+
+## Save Payment Methods
+
+If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+
+- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
+- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
+- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
+- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
+
+Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
+
+***
+
+## Account Holder in Medusa Payment Flows
+
+In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
+
+Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
+
+This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
+
+
+# Links between Payment Module and Other Modules
+
+This document showcases the module links defined between the Payment Module and other commerce modules.
+
+## Summary
+
+The Payment Module has the following links to other modules:
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Cart Module
+
+The Cart Module provides cart-related features, but not payment processing.
+
+Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
+
+Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### Manage with Link
+
+To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Customer Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+This link is available starting from Medusa `v2.5.0`.
+
+### Retrieve with Query
+
+To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: accountHolders } = await query.graph({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: accountHolders } = useQueryGraphStep({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
+
+So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### Manage with Link
+
+To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Region Module
+
+You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
+
+
+
+This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
+
+### Retrieve with Query
+
+To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentProviders } = await query.graph({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentProviders } = useQueryGraphStep({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### Manage with Link
+
+To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+
+# Payment
+
+In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
+
+## What's a Payment?
+
+When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
+
+A payment carries many of the data and relations of a payment session:
+
+- It belongs to the same payment collection.
+- It’s associated with the same payment provider, which handles further payment processing.
+- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
+
+***
+
+## Capture Payments
+
+When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
+
+The payment can also be captured incrementally, each time a capture record is created for that amount.
+
+
+
+***
+
+## Refund Payments
+
+When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
+
+A payment can be refunded multiple times, and each time a refund record is created.
+
+
+
+
+# Payment Module Options
+
+In this document, you'll learn about the options of the Payment Module.
+
+## All Module Options
+
+|Option|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
+|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
+|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
+
+***
+
+## providers Option
+
+The `providers` option is an array of payment module providers.
+
+When the Medusa application starts, these providers are registered and can be used to process payments.
+
+For example:
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/payment",
+ options: {
+ providers: [
+ {
+ resolve: "@medusajs/medusa/payment-stripe",
+ id: "stripe",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The `providers` option is an array of objects that accept the following properties:
+
+- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+
+# Payment Collection
+
+In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
+
+## What's a Payment Collection?
+
+A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
+
+Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
+
+- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
+- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
+- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
+
+***
+
+## Multiple Payments
+
+The payment collection supports multiple payment sessions and payments.
+
+You can use this to accept payments in increments or split payments across payment providers.
+
+
+
+***
+
+## Usage with the Cart Module
+
+The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
+
+During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
+
+It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
+
+
+
+
+# Links between Currency Module and Other Modules
+
+This document showcases the module links defined between the Currency Module and other commerce modules.
+
+## Summary
+
+The Currency Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Read-only||
+
+***
+
+## Store Module
+
+The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
+
+Instead, Medusa defines a read-only link between the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model and the Currency Module's `Currency` data model. Because the link is read-only from the `Store`'s side, you can only retrieve the details of a store's supported currencies, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: stores } = await query.graph({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+
+# Accept Payment Flow
+
+In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
+
+It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
+
+For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
+
+## Flow Overview
+
+
+
+***
+
+## 1. Create a Payment Collection
+
+A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+await createPaymentCollectionForCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: "cart_123",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentCollection =
+ await paymentModuleService.createPaymentCollections({
+ currency_code: "usd",
+ amount: 5000,
+ })
+```
+
+***
+
+## 2. Create Payment Sessions
+
+The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
+
+So, after creating the payment collection, create at least one payment session for a provider.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
+ .run({
+ input: {
+ payment_collection_id: "paycol_123",
+ provider_id: "stripe",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentSession =
+ await paymentModuleService.createPaymentSession(
+ paymentCollection.id,
+ {
+ provider_id: "stripe",
+ currency_code: "usd",
+ amount: 5000,
+ data: {
+ // any necessary data for the
+ // payment provider
+ },
+ }
+ )
+```
+
+***
+
+## 3. Authorize Payment Session
+
+Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
+
+For example:
+
+### Using Step
+
+```ts
+import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+### Using Service
+
+```ts
+const payment = authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+When the payment authorization is successful, a payment is created and returned.
+
+### Handling Additional Action
+
+If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
+
+If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
+
+In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
+
+For example:
+
+```ts
+try {
+ const payment =
+ await paymentModuleService.authorizePaymentSession(
+ paymentSession.id,
+ {}
+ )
+} catch (e) {
+ // retrieve the payment session again
+ const updatedPaymentSession = (
+ await paymentModuleService.listPaymentSessions({
+ id: [paymentSession.id],
+ })
+ )[0]
+
+ if (updatedPaymentSession.status === "requires_more") {
+ // TODO perform required action
+ // TODO authorize payment again.
+ }
+}
+```
+
+***
+
+## 4. Payment Flow Complete
+
+The payment flow is complete once the payment session is authorized and the payment is created.
+
+You can then:
+
+- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
+- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
+
+Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
+
+
+# Payment Module Provider
+
+In this document, you’ll learn what a payment module provider is.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/regions/index.html.md) to learn how to manage the payment providers available in a region using the dashboard.
+
+## What's a Payment Module Provider?
+
+A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
+
+To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
+
+After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
+
+### List of Payment Module Providers
+
+- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
+
+***
+
+## System Payment Provider
+
+The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
+
+It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
+
+***
+
+## How are Payment Providers Created?
+
+A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
+
+Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
+
+***
+
+## Configure Payment Providers
+
+The Payment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
+
+***
+
+## PaymentProvider Data Model
+
+When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
+
+This data model is used to reference a payment provider and determine whether it’s installed in the application.
+
+
+# Webhook Events
+
+In this document, you’ll learn how the Payment Module supports listening to webhook events.
+
+## What's a Webhook Event?
+
+A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
+
+This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
+
+***
+
+## getWebhookActionAndData Method
+
+The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
+
+Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
+
+- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
+- `[provider]` is the ID of the provider. For example, `stripe`.
+
+For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
+
+Use that webhook listener in your third-party payment provider's configurations.
+
+
+
+If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
+
+If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
+
+### Actions After Webhook Payment Processing
+
+After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
+
+
+# Payment Session
+
+In this document, you’ll learn what a payment session is.
+
+## What's a Payment Session?
+
+A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
+
+A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
+
+
+
+***
+
+## data Property
+
+Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
+
+For example, the customer's ID in Stripe is stored in the `data` property.
+
+***
+
+## Payment Session Status
+
+The `status` property of a payment session indicates its current status. Its value can be:
+
+- `pending`: The payment session is awaiting authorization.
+- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
+- `authorized`: The payment session is authorized.
+- `error`: An error occurred while authorizing the payment.
+- `canceled`: The authorization of the payment session has been canceled.
+
+
+# Links between Pricing Module and Other Modules
+
+This document showcases the module links defined between the Pricing Module and other commerce modules.
+
+## Summary
+
+The Pricing Module has the following links to other modules:
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Fulfillment Module
+
+The Fulfillment Module provides fulfillment-related functionalities, including shipping options that the customer chooses from when they place their order. However, it doesn't provide pricing-related functionalities for these options.
+
+Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
+
+
+
+### Retrieve with Query
+
+To retrieve the shipping option of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_option.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
+ ],
+})
+
+// priceSets.shipping_option
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
+ ],
+})
+
+// priceSets.shipping_option
+```
+
+### Manage with Link
+
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+***
+
+## Product Module
+
+The Product Module doesn't store or manage the prices of product variants.
+
+Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
+
+
+
+So, when you want to add prices for a product variant, you create a price set and add the prices to it.
+
+You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
+
+### Retrieve with Query
+
+To retrieve the variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// priceSets.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// priceSets.variant
+```
+
+### Manage with Link
+
+To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+
+# Prices Calculation
+
+In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
+
+## calculatePrices Method
+
+The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
+
+It returns a price object with the best matching price for each price set.
+
+### Calculation Context
+
+The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
+
+For example:
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSetId] },
+ {
+ context: {
+ currency_code: currencyCode,
+ region_id: "reg_123",
+ },
+ }
+)
+```
+
+In this example, you retrieve the prices in a price set for the specified currency code and region ID.
+
+### Returned Price Object
+
+For each price set, the `calculatePrices` method selects two prices:
+
+- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
+- An original price, which is either:
+ - The same price as the calculated price if the price list it belongs to is of type `override`;
+ - Or a price that doesn't belong to a price list and best matches the specified context.
+
+Both prices are returned in an object that has the following properties:
+
+- id: (\`string\`) The ID of the price set from which the price was selected.
+- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
+- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
+- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
+- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
+- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
+- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- calculated\_price: (\`object\`) The calculated price's price details.
+
+ - id: (\`string\`) The ID of the price.
+
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
+
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
+
+ - min\_quantity: (\`number\`) The price's min quantity condition.
+
+ - max\_quantity: (\`number\`) The price's max quantity condition.
+- original\_price: (\`object\`) The original price's price details.
+
+ - id: (\`string\`) The ID of the price.
+
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
+
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
+
+ - min\_quantity: (\`number\`) The price's min quantity condition.
+
+ - max\_quantity: (\`number\`) The price's max quantity condition.
+
+***
+
+## Examples
+
+Consider the following price set:
+
+```ts
+const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
+ // default price
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {},
+ },
+ // prices with rules
+ {
+ amount: 400,
+ currency_code: "EUR",
+ rules: {
+ region_id: "reg_123",
+ },
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ rules: {
+ city: "krakow",
+ },
+ },
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {
+ city: "warsaw",
+ region_id: "reg_123",
+ },
+ },
+ ],
+})
+```
+
+### Default Price Selection
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR"
+ }
+ }
+)
+```
+
+### Result
+
+### Calculate Prices with Rules
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "reg_123",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+### Price Selection with Price List
+
+### Code
+
+```ts
+const priceList = pricingModuleService.createPriceLists([{
+ title: "Summer Price List",
+ description: "Price list for summer sale",
+ starts_at: Date.parse("01/10/2023").toString(),
+ ends_at: Date.parse("31/10/2023").toString(),
+ rules: {
+ region_id: ['PL']
+ },
+ type: "sale",
+ prices: [
+ {
+ amount: 400,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ ],
+}]);
+
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "PL",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+
+# Pricing Concepts
+
+In this document, you’ll learn about the main concepts in the Pricing Module.
+
+## Price Set
+
+A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option).
+
+Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
+
+
+
+***
+
+## Price List
+
+A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied.
+
+A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
+
+Its associated prices are represented by the `Price` data model.
+
+
+# Price Rules
+
+In this document, you'll learn about price rules for price sets and price lists.
+
+## Price Rule
+
+You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md).
+
+The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
+
+For exmaple, you create a price restricted to `10557` zip codes.
+
+
+
+A price can have multiple price rules.
+
+For example, a price can be restricted by a region and a zip code.
+
+
+
+***
+
+## Price List Rules
+
+Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
+
+The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
+
+
+
+
+# Tax-Inclusive Pricing
+
+In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
+
+## What is Tax-Inclusive Pricing?
+
+A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
+
+For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
+
+***
+
+## How is Tax-Inclusive Pricing Set?
+
+The [PricePreference data model](https://docs.medusajs.com/references/pricing/models/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
+
+- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
+- `value`: The attribute’s value. For example, `reg_123` or `usd`.
+
+Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
+
+The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
+
+For example:
+
+```json
+{
+ "attribute": "currency_code",
+ "value": "USD",
+ "is_tax_inclusive": true,
+}
+```
+
+In this example, tax-inclusivity is enabled for the `USD` currency code.
+
+***
+
+## Tax-Inclusive Pricing in Price Calculation
+
+### Tax Context
+
+As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
+
+To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
+
+### Returned Tax Properties
+
+The `calculatePrices` method returns two properties related to tax-inclusivity:
+
+Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
+
+- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
+- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
+
+A price is considered tax-inclusive if:
+
+1. It belongs to the region or currency code specified in the calculation context;
+2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
+
+### Tax Context Precedence
+
+A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
+
+- both the `region_id` and `currency_code` are provided in the calculation context;
+- the selected price belongs to the region;
+- and the region has a price preference
+
+
+# Promotion Actions
+
+In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
+
+## computeActions Method
+
+The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
+
+Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
+
+***
+
+## Action Types
+
+### `addItemAdjustment` Action
+
+The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
+
+This action has the following format:
+
+```ts
+export interface AddItemAdjustmentAction {
+ action: "addItemAdjustment"
+ item_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `removeItemAdjustment` Action
+
+The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
+
+The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveItemAdjustmentAction {
+ action: "removeItemAdjustment"
+ adjustment_id: string
+ description?: string
+ code: string
+}
+```
+
+This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `addShippingMethodAdjustment` Action
+
+The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
+
+This action has the following format:
+
+```ts
+export interface AddShippingMethodAdjustment {
+ action: "addShippingMethodAdjustment"
+ shipping_method_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `removeShippingMethodAdjustment` Action
+
+The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
+
+The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveShippingMethodAdjustment {
+ action: "removeShippingMethodAdjustment"
+ adjustment_id: string
+ code: string
+}
+```
+
+When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `campaignBudgetExceeded` Action
+
+When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
+
+This action has the following format:
+
+```ts
+export interface CampaignBudgetExceededAction {
+ action: "campaignBudgetExceeded"
+ code: string
+}
+```
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
+
+
+# Promotion Concepts
+
+In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/index.html.md) to learn how to manage promotions using the dashboard.
+
+## What is a Promotion?
+
+A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders.
+
+A promotion has two types:
+
+- `standard`: A standard promotion with rules.
+- `buyget`: “A buy X get Y” promotion with rules.
+
+|\`standard\`|\`buyget\`|
+|---|---|
+|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.|
+|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.|
+|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.|
+
+The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes.
+
+***
+
+## PromotionRule
+
+A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md).
+
+For example, you can create a promotion that only customers of the `VIP` customer group can use.
+
+
+
+A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied.
+
+For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
+
+When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself.
+
+For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
+
+***
+
+## Flexible Rules
+
+The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`).
+
+For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
+
+- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
+- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`.
+
+
+
+In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion.
+
+
+# Application Method
+
+In this document, you'll learn what an application method is.
+
+## What is an Application Method?
+
+The [ApplicationMethod data model](https://docs.medusajs.com/references/promotion/models/ApplicationMethod/index.html.md) defines how a promotion is applied:
+
+|Property|Purpose|
+|---|---|
+|\`type\`|Does the promotion discount a fixed amount or a percentage?|
+|\`target\_type\`|Is the promotion applied on a cart item, shipping method, or the entire order?|
+|\`allocation\`|Is the discounted amount applied on each item or split between the applicable items?|
+
+## Target Promotion Rules
+
+When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to.
+
+The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` property represents this relation.
+
+
+
+In this example, the promotion is only applied on products in the cart having the SKU `SHIRT`.
+
+***
+
+## Buy Promotion Rules
+
+When the promotion’s type is `buyget`, you must specify the “buy X” condition. For example, a cart must have two shirts before the promotion can be applied.
+
+The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation.
+
+
+
+In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied.
+
+
+# Campaign
+
+In this document, you'll learn about campaigns.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/campaigns/index.html.md) to learn how to manage campaigns using the dashboard.
+
+## What is a Campaign?
+
+A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates.
+
+
+
+***
+
+## Campaign Limits
+
+Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used.
+
+There are two types of budgets:
+
+- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
+- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
+
+
+
+
+# Links between Promotion Module and Other Modules
+
+This document showcases the module links defined between the Promotion Module and other commerce modules.
+
+## Summary
+
+The Promotion Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Read-only||
+| in ||Stored||
+
+***
+
+## Cart Module
+
+A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models.
+
+
+
+Medusa also defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model and the `Promotion` data model. Because the link is read-only from the `LineItemAdjustment`'s side, you can only retrieve the promotion applied on a line item, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
+
+To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: promotions } = await query.graph({
+ entity: "promotion",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// promotions.carts
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: promotions } = useQueryGraphStep({
+ entity: "promotion",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// promotions.carts
+```
+
+### Manage with Link
+
+To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: promotions } = await query.graph({
+ entity: "promotion",
+ fields: [
+ "orders.*",
+ ],
+})
+
+// promotions.orders
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: promotions } = useQueryGraphStep({
+ entity: "promotion",
+ fields: [
+ "orders.*",
+ ],
+})
+
+// promotions.orders
+```
+
+### Manage with Link
+
+To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+
+# Links between Product Module and Other Modules
+
+This document showcases the module links defined between the Product Module and other commerce modules.
+
+## Summary
+
+The Product Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Read-only||
+|| in |Stored||
+|| in |Stored||
+| in ||Read-only||
+|| in |Stored||
+|| in |Stored||
+
+***
+
+## Cart Module
+
+Medusa defines read-only links between:
+
+- The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model and the `Product` data model. Because the link is read-only from the `LineItem`'s side, you can only retrieve the product of a line item, and not the other way around.
+- The `ProductVariant` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. Because the link is read-only from the `LineItem`'s side, you can only retrieve the variant of a line item, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+To retrieve the product, pass `product.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: lineItems } = await query.graph({
+ entity: "line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: lineItems } = useQueryGraphStep({
+ entity: "line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+***
+
+## Fulfillment Module
+
+Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile.
+
+This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
+
+### Retrieve with Query
+
+To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "shipping_profile.*",
+ ],
+})
+
+// products.shipping_profile
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: [
+ "shipping_profile.*",
+ ],
+})
+
+// products.shipping_profile
+```
+
+### Manage with Link
+
+To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+***
+
+## Inventory Module
+
+The Inventory Module provides inventory-management features for any stock-kept item.
+
+Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details.
+
+
+
+When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
+
+Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the inventory items of a product variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_items.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "inventory_items.*",
+ ],
+})
+
+// variants.inventory_items
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "inventory_items.*",
+ ],
+})
+
+// variants.inventory_items
+```
+
+### Manage with Link
+
+To manage the inventory items of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+Medusa defines read-only links between:
+
+- the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model and the `Product` data model. Because the link is read-only from the `OrderLineItem`'s side, you can only retrieve the product of an order line item, and not the other way around.
+- the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model and the `ProductVariant` data model. Because the link is read-only from the `OrderLineItem`'s side, you can only retrieve the variant of an order line item, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+To retrieve the product, pass `product.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: lineItems } = await query.graph({
+ entity: "order_line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: lineItems } = useQueryGraphStep({
+ entity: "order_line_item",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// lineItems.variant
+```
+
+***
+
+## Pricing Module
+
+The Product Module doesn't provide pricing-related features.
+
+Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set.
+
+
+
+So, to add prices for a product variant, create a price set and add the prices to it.
+
+### Retrieve with Query
+
+To retrieve the price set of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// variants.price_set
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// variants.price_set
+```
+
+### Manage with Link
+
+To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+***
+
+## Sales Channel Module
+
+The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
+
+Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
+
+
+
+### Retrieve with Query
+
+To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// products.sales_channels
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// products.sales_channels
+```
+
+### Manage with Link
+
+To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+
+# Product Variant Inventory
+
+# Product Variant Inventory
+
+In this guide, you'll learn about the inventory management features related to product variants.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/products/variants#manage-product-variant-inventory/index.html.md) to learn how to manage inventory of product variants.
+
+## Configure Inventory Management of Product Variants
+
+A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
+
+The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
+
+When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
+
+***
+
+## How the Medusa Application Manages Inventory
+
+When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
+
+
+
+The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
+
+
+
+Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
+
+The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
+
+
+
+Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
+
+### Product Inventory in Storefronts
+
+When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
+
+The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
+
+For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
+
+
+
+***
+
+## Variant Back Orders
+
+Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
+
+You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
+
+***
+
+## Additional Resources
+
+The following guides provide more details on inventory management in the Medusa application:
+
+- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
+- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
+- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
+- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
+
+
+# Configure Selling Products
+
+In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
+
+The concepts in this guide are applicable starting from Medusa v2.5.1.
+
+## Scenario
+
+Businesses can have different selling requirements:
+
+1. They may sell physical or digital items.
+2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
+3. They may sell items whose inventory is managed by an external system, such as an ERP.
+
+Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
+
+***
+
+## Configuring Shipping Requirements
+
+The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
+
+When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
+
+If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
+
+### Overriding Shipping Requirements for Variants
+
+A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
+
+Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
+
+When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
+
+1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
+2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
+
+***
+
+## Use Case Examples
+
+By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
+
+|Use Case|Configurations|Example|
+|---|---|---|---|---|
+|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
+|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
+|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
+|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
+
+
+# Stock Location Concepts
+
+In this document, you’ll learn about the main concepts in the Stock Location Module.
+
+## Stock Location
+
+A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
+
+Medusa uses stock locations to provide inventory details, from the Inventory Module, per location.
+
+***
+
+## StockLocationAddress
+
+The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address.
+
+
+# Links between Region Module and Other Modules
+
+This document showcases the module links defined between the Region Module and other commerce modules.
+
+## Summary
+
+The Region Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Read-only||
+| in ||Read-only||
+|| in |Stored||
+
+***
+
+## Cart Module
+
+Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `Region` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the region of a cart, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: carts } = await query.graph({
+ entity: "cart",
+ fields: [
+ "region.*",
+ ],
+})
+
+// carts.region
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: [
+ "region.*",
+ ],
+})
+
+// carts.region
+```
+
+***
+
+## Order Module
+
+Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `Region` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the region of an order, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: orders } = await query.graph({
+ entity: "order",
+ fields: [
+ "region.*",
+ ],
+})
+
+// orders.region
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "region.*",
+ ],
+})
+
+// orders.region
+```
+
+***
+
+## Payment Module
+
+You can specify for each region which payment providers are available for use.
+
+Medusa defines a module link between the `PaymentProvider` and the `Region` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: regions } = await query.graph({
+ entity: "region",
+ fields: [
+ "payment_providers.*",
+ ],
+})
+
+// regions.payment_providers
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: regions } = useQueryGraphStep({
+ entity: "region",
+ fields: [
+ "payment_providers.*",
+ ],
+})
+
+// regions.payment_providers
+```
+
+### Manage with Link
+
+To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+
+# Links between Stock Location Module and Other Modules
+
+This document showcases the module links defined between the Stock Location Module and other commerce modules.
+
+## Summary
+
+The Stock Location Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+| in ||Stored||
+
+***
+
+## Fulfillment Module
+
+A fulfillment set can be conditioned to a specific stock location.
+
+Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
+
+
+
+Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
+
+
+
+### Retrieve with Query
+
+To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`:
+
+To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "fulfillment_sets.*",
+ ],
+})
+
+// stockLocations.fulfillment_sets
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "fulfillment_sets.*",
+ ],
+})
+
+// stockLocations.fulfillment_sets
+```
+
+### Manage with Link
+
+To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
+
+***
+
+## Inventory Module
+
+Medusa defines a read-only link between the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model and the `StockLocation` data model. Because the link is read-only from the `InventoryLevel`'s side, you can only retrieve the stock location of an inventory level, and not the other way around.
+
+### Retrieve with Query
+
+To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: inventoryLevels } = await query.graph({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: inventoryLevels } = useQueryGraphStep({
+ entity: "inventory_level",
+ fields: [
+ "stock_locations.*",
+ ],
+})
+
+// inventoryLevels.stock_locations
+```
+
+***
+
+## Sales Channel Module
+
+A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
+
+Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// stockLocations.sales_channels
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// stockLocations.sales_channels
+```
+
+### Manage with Link
+
+To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
# Links between Sales Channel Module and Other Modules
@@ -26783,1021 +27641,6 @@ The Medusa application infers the associated sales channels and ensures that onl
To create a publishable API key, either use the [Medusa Admin](https://docs.medusajs.com/user-guide/settings/developer/publishable-api-keys/index.html.md) or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys).
-# Promotion Actions
-
-In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
-
-## computeActions Method
-
-The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
-
-Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
-
-***
-
-## Action Types
-
-### `addItemAdjustment` Action
-
-The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
-
-This action has the following format:
-
-```ts
-export interface AddItemAdjustmentAction {
- action: "addItemAdjustment"
- item_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `removeItemAdjustment` Action
-
-The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
-
-The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveItemAdjustmentAction {
- action: "removeItemAdjustment"
- adjustment_id: string
- description?: string
- code: string
-}
-```
-
-This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `addShippingMethodAdjustment` Action
-
-The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
-
-This action has the following format:
-
-```ts
-export interface AddShippingMethodAdjustment {
- action: "addShippingMethodAdjustment"
- shipping_method_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `removeShippingMethodAdjustment` Action
-
-The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
-
-The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveShippingMethodAdjustment {
- action: "removeShippingMethodAdjustment"
- adjustment_id: string
- code: string
-}
-```
-
-When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `campaignBudgetExceeded` Action
-
-When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
-
-This action has the following format:
-
-```ts
-export interface CampaignBudgetExceededAction {
- action: "campaignBudgetExceeded"
- code: string
-}
-```
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
-
-
-# Promotion Concepts
-
-In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/index.html.md) to learn how to manage promotions using the dashboard.
-
-## What is a Promotion?
-
-A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders.
-
-A promotion has two types:
-
-- `standard`: A standard promotion with rules.
-- `buyget`: “A buy X get Y” promotion with rules.
-
-|\`standard\`|\`buyget\`|
-|---|---|
-|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.|
-|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.|
-|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.|
-
-The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes.
-
-***
-
-## PromotionRule
-
-A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md).
-
-For example, you can create a promotion that only customers of the `VIP` customer group can use.
-
-
-
-A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied.
-
-For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
-
-When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself.
-
-For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
-
-***
-
-## Flexible Rules
-
-The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`).
-
-For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
-
-- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
-- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`.
-
-
-
-In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion.
-
-
-# Campaign
-
-In this document, you'll learn about campaigns.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/promotions/campaigns/index.html.md) to learn how to manage campaigns using the dashboard.
-
-## What is a Campaign?
-
-A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates.
-
-
-
-***
-
-## Campaign Limits
-
-Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used.
-
-There are two types of budgets:
-
-- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
-- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
-
-
-
-
-# Application Method
-
-In this document, you'll learn what an application method is.
-
-## What is an Application Method?
-
-The [ApplicationMethod data model](https://docs.medusajs.com/references/promotion/models/ApplicationMethod/index.html.md) defines how a promotion is applied:
-
-|Property|Purpose|
-|---|---|
-|\`type\`|Does the promotion discount a fixed amount or a percentage?|
-|\`target\_type\`|Is the promotion applied on a cart item, shipping method, or the entire order?|
-|\`allocation\`|Is the discounted amount applied on each item or split between the applicable items?|
-
-## Target Promotion Rules
-
-When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to.
-
-The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` property represents this relation.
-
-
-
-In this example, the promotion is only applied on products in the cart having the SKU `SHIRT`.
-
-***
-
-## Buy Promotion Rules
-
-When the promotion’s type is `buyget`, you must specify the “buy X” condition. For example, a cart must have two shirts before the promotion can be applied.
-
-The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation.
-
-
-
-In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied.
-
-
-# Links between Promotion Module and Other Modules
-
-This document showcases the module links defined between the Promotion Module and other commerce modules.
-
-## Summary
-
-The Promotion Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Read-only||
-| in ||Stored||
-
-***
-
-## Cart Module
-
-A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models.
-
-
-
-Medusa also defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model and the `Promotion` data model. Because the link is read-only from the `LineItemAdjustment`'s side, you can only retrieve the promotion applied on a line item, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
-
-To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: promotions } = await query.graph({
- entity: "promotion",
- fields: [
- "carts.*",
- ],
-})
-
-// promotions.carts
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: promotions } = useQueryGraphStep({
- entity: "promotion",
- fields: [
- "carts.*",
- ],
-})
-
-// promotions.carts
-```
-
-### Manage with Link
-
-To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: promotions } = await query.graph({
- entity: "promotion",
- fields: [
- "orders.*",
- ],
-})
-
-// promotions.orders
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: promotions } = useQueryGraphStep({
- entity: "promotion",
- fields: [
- "orders.*",
- ],
-})
-
-// promotions.orders
-```
-
-### Manage with Link
-
-To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-
-# Stock Location Concepts
-
-In this document, you’ll learn about the main concepts in the Stock Location Module.
-
-## Stock Location
-
-A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
-
-Medusa uses stock locations to provide inventory details, from the Inventory Module, per location.
-
-***
-
-## StockLocationAddress
-
-The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address.
-
-
-# Links between Stock Location Module and Other Modules
-
-This document showcases the module links defined between the Stock Location Module and other commerce modules.
-
-## Summary
-
-The Stock Location Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-| in ||Stored||
-
-***
-
-## Fulfillment Module
-
-A fulfillment set can be conditioned to a specific stock location.
-
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
-
-
-
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
-
-
-
-### Retrieve with Query
-
-To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`:
-
-To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "fulfillment_sets.*",
- ],
-})
-
-// stockLocations.fulfillment_sets
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "fulfillment_sets.*",
- ],
-})
-
-// stockLocations.fulfillment_sets
-```
-
-### Manage with Link
-
-To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-***
-
-## Inventory Module
-
-Medusa defines a read-only link between the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model and the `StockLocation` data model. Because the link is read-only from the `InventoryLevel`'s side, you can only retrieve the stock location of an inventory level, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryLevels } = await query.graph({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryLevels } = useQueryGraphStep({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-***
-
-## Sales Channel Module
-
-A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
-
-Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// stockLocations.sales_channels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// stockLocations.sales_channels
-```
-
-### Manage with Link
-
-To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-
-
-# Links between Region Module and Other Modules
-
-This document showcases the module links defined between the Region Module and other commerce modules.
-
-## Summary
-
-The Region Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-| in ||Read-only||
-| in ||Read-only||
-|| in |Stored||
-
-***
-
-## Cart Module
-
-Medusa defines a read-only link between the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model and the `Region` data model. Because the link is read-only from the `Cart`'s side, you can only retrieve the region of a cart, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: carts } = await query.graph({
- entity: "cart",
- fields: [
- "region.*",
- ],
-})
-
-// carts.region
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: carts } = useQueryGraphStep({
- entity: "cart",
- fields: [
- "region.*",
- ],
-})
-
-// carts.region
-```
-
-***
-
-## Order Module
-
-Medusa defines a read-only link between the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model and the `Region` data model. Because the link is read-only from the `Order`'s side, you can only retrieve the region of an order, and not the other way around.
-
-### Retrieve with Query
-
-To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: orders } = await query.graph({
- entity: "order",
- fields: [
- "region.*",
- ],
-})
-
-// orders.region
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "region.*",
- ],
-})
-
-// orders.region
-```
-
-***
-
-## Payment Module
-
-You can specify for each region which payment providers are available for use.
-
-Medusa defines a module link between the `PaymentProvider` and the `Region` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: regions } = await query.graph({
- entity: "region",
- fields: [
- "payment_providers.*",
- ],
-})
-
-// regions.payment_providers
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: regions } = useQueryGraphStep({
- entity: "region",
- fields: [
- "payment_providers.*",
- ],
-})
-
-// regions.payment_providers
-```
-
-### Manage with Link
-
-To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-
-# User Creation Flows
-
-In this document, learn the different ways to create a user using the User Module.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/users/index.html.md) to learn how to manage users using the dashboard.
-
-## Straightforward User Creation
-
-To create a user, use the [create method of the User Module’s main service](https://docs.medusajs.com/references/user/create/index.html.md):
-
-```ts
-const user = await userModuleService.createUsers({
- email: "user@example.com",
-})
-```
-
-You can pair this with the Auth Module to allow the user to authenticate, as explained in a [later section](#create-identity-with-the-auth-module).
-
-***
-
-## Invite Users
-
-To create a user, you can create an invite for them using the [createInvites method](https://docs.medusajs.com/references/user/createInvites/index.html.md) of the User Module's main service:
-
-```ts
-const invite = await userModuleService.createInvites({
- email: "user@example.com",
-})
-```
-
-Later, you can accept the invite and create a new user for them:
-
-```ts
-const invite =
- await userModuleService.validateInviteToken("secret_123")
-
-await userModuleService.updateInvites({
- id: invite.id,
- accepted: true,
-})
-
-const user = await userModuleService.createUsers({
- email: invite.email,
-})
-```
-
-### Invite Expiry
-
-An invite has an expiry date. You can renew the expiry date and refresh the token using the [refreshInviteTokens method](https://docs.medusajs.com/references/user/refreshInviteTokens/index.html.md):
-
-```ts
-await userModuleService.refreshInviteTokens(["invite_123"])
-```
-
-***
-
-## Create Identity with the Auth Module
-
-By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users.
-
-So, when a user is authenticated, and you receive the `AuthIdentity` object, you can use it to create a user if it doesn’t exist:
-
-```ts
-const { success, authIdentity } =
- await authModuleService.authenticate("emailpass", {
- // ...
- })
-
-const [, count] = await userModuleService.listAndCountUsers({
- email: authIdentity.entity_id,
-})
-
-if (!count) {
- const user = await userModuleService.createUsers({
- email: authIdentity.entity_id,
- })
-}
-```
-
-
-# User Module Options
-
-In this document, you'll learn about the options of the User Module.
-
-## Module Options
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/user",
- options: {
- jwt_secret: process.env.JWT_SECRET,
- },
- },
- ],
-})
-```
-
-|Option|Description|Required|
-|---|---|---|---|---|
-|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes|
-
-### Environment Variables
-
-Make sure to add the necessary environment variables for the above options in `.env`:
-
-```bash
-JWT_SECRET=supersecret
-```
-
-
-# Links between Store Module and Other Modules
-
-This document showcases the module links defined between the Store Module and other commerce modules.
-
-## Summary
-
-The Store Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-|First Data Model|Second Data Model|Type|Description|
-|---|---|---|---|
-|| in |Read-only||
-
-***
-
-## Currency Module
-
-The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
-
-Instead, Medusa defines a read-only link between the [Currency Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/index.html.md)'s `Currency` data model and the Store Module's `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the [Currency](https://docs.medusajs.com/references/store/models/Currency/index.html.md) data model in the Store Module (not in the Currency Module).
-
-### Retrieve with Query
-
-To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stores } = await query.graph({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
-
-// stores.supported_currencies
-```
-
-
# Tax Module Options
In this document, you'll learn about the options of the Tax Module.
@@ -27918,21 +27761,6 @@ TODO add once tax provider guide is updated + add module providers match other m
Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
-# Tax Region
-
-In this document, you’ll learn about tax regions and how to use them with the Region Module.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
-
-## What is a Tax Region?
-
-A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
-
-Tax regions can inherit settings and rules from a parent tax region.
-
-Each tax region has tax rules and a tax provider.
-
-
# Tax Rates and Rules
In this document, you’ll learn about tax rates and rules.
@@ -27971,6 +27799,277 @@ These two properties of the data model identify the rule’s target:
So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type.
+# Tax Region
+
+In this document, you’ll learn about tax regions and how to use them with the Region Module.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions/index.html.md) to learn how to manage tax regions using the dashboard.
+
+## What is a Tax Region?
+
+A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
+
+Tax regions can inherit settings and rules from a parent tax region.
+
+Each tax region has tax rules and a tax provider.
+
+
+# User Module Options
+
+In this document, you'll learn about the options of the User Module.
+
+## Module Options
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/user",
+ options: {
+ jwt_secret: process.env.JWT_SECRET,
+ },
+ },
+ ],
+})
+```
+
+|Option|Description|Required|
+|---|---|---|---|---|
+|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes|
+
+### Environment Variables
+
+Make sure to add the necessary environment variables for the above options in `.env`:
+
+```bash
+JWT_SECRET=supersecret
+```
+
+
+# User Creation Flows
+
+In this document, learn the different ways to create a user using the User Module.
+
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/users/index.html.md) to learn how to manage users using the dashboard.
+
+## Straightforward User Creation
+
+To create a user, use the [create method of the User Module’s main service](https://docs.medusajs.com/references/user/create/index.html.md):
+
+```ts
+const user = await userModuleService.createUsers({
+ email: "user@example.com",
+})
+```
+
+You can pair this with the Auth Module to allow the user to authenticate, as explained in a [later section](#create-identity-with-the-auth-module).
+
+***
+
+## Invite Users
+
+To create a user, you can create an invite for them using the [createInvites method](https://docs.medusajs.com/references/user/createInvites/index.html.md) of the User Module's main service:
+
+```ts
+const invite = await userModuleService.createInvites({
+ email: "user@example.com",
+})
+```
+
+Later, you can accept the invite and create a new user for them:
+
+```ts
+const invite =
+ await userModuleService.validateInviteToken("secret_123")
+
+await userModuleService.updateInvites({
+ id: invite.id,
+ accepted: true,
+})
+
+const user = await userModuleService.createUsers({
+ email: invite.email,
+})
+```
+
+### Invite Expiry
+
+An invite has an expiry date. You can renew the expiry date and refresh the token using the [refreshInviteTokens method](https://docs.medusajs.com/references/user/refreshInviteTokens/index.html.md):
+
+```ts
+await userModuleService.refreshInviteTokens(["invite_123"])
+```
+
+***
+
+## Create Identity with the Auth Module
+
+By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users.
+
+So, when a user is authenticated, and you receive the `AuthIdentity` object, you can use it to create a user if it doesn’t exist:
+
+```ts
+const { success, authIdentity } =
+ await authModuleService.authenticate("emailpass", {
+ // ...
+ })
+
+const [, count] = await userModuleService.listAndCountUsers({
+ email: authIdentity.entity_id,
+})
+
+if (!count) {
+ const user = await userModuleService.createUsers({
+ email: authIdentity.entity_id,
+ })
+}
+```
+
+
+# Links between Store Module and Other Modules
+
+This document showcases the module links defined between the Store Module and other commerce modules.
+
+## Summary
+
+The Store Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+|First Data Model|Second Data Model|Type|Description|
+|---|---|---|---|
+|| in |Read-only||
+
+***
+
+## Currency Module
+
+The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol.
+
+Instead, Medusa defines a read-only link between the [Currency Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/index.html.md)'s `Currency` data model and the Store Module's `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the [Currency](https://docs.medusajs.com/references/store/models/Currency/index.html.md) data model in the Store Module (not in the Currency Module).
+
+### Retrieve with Query
+
+To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: stores } = await query.graph({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+
+# GitHub Auth Module Provider
+
+In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
+
+The Github Auth Module Provider authenticates users with their GitHub account.
+
+Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
+
+***
+
+## Register the Github Auth Module Provider
+
+### Prerequisites
+
+- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
+- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication)
+
+Add the module to the array of providers passed to the Auth Module:
+
+```ts title="medusa-config.ts"
+import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/auth",
+ dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
+ options: {
+ providers: [
+ // other providers...
+ {
+ resolve: "@medusajs/medusa/auth-github",
+ id: "github",
+ options: {
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ callbackUrl: process.env.GITHUB_CALLBACK_URL,
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+### Environment Variables
+
+Make sure to add the necessary environment variables for the above options in `.env`:
+
+```plain
+GITHUB_CLIENT_ID=
+GITHUB_CLIENT_SECRET=
+GITHUB_CALLBACK_URL=
+```
+
+### Module Options
+
+|Configuration|Description|Required|
+|---|---|---|---|---|
+|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes|
+|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes|
+|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes|
+
+***
+
+## Override Callback URL During Authentication
+
+In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication.
+
+The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md).
+
+***
+
+## Examples
+
+- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+
+
# Emailpass Auth Module Provider
In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
@@ -28120,27 +28219,24 @@ The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednass
- [How to implement Google social login in the storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
-# GitHub Auth Module Provider
+# Stripe Module Provider
-In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
+In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module.
-The Github Auth Module Provider authenticates users with their GitHub account.
+Your technical team must install the Stripe Module Provider in your Medusa application first. Then, refer to [this user guide](https://docs.medusajs.com/user-guide/settings/regions#edit-region-details/index.html.md) to learn how to enable the Stripe payment provider in a region using the Medusa Admin dashboard.
-Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
-
-***
-
-## Register the Github Auth Module Provider
+## Register the Stripe Module Provider
### Prerequisites
-- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
-- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication)
+- [Stripe account](https://stripe.com/)
+- [Stripe Secret API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard)
+- [For deployed Medusa applications, a Stripe webhook secret. Refer to the end of this guide for details on the URL and events.](https://docs.stripe.com/webhooks#add-a-webhook-endpoint)
-Add the module to the array of providers passed to the Auth Module:
+The Stripe Module Provider is installed by default in your application. To use it, add it to the array of providers passed to the Payment Module in `medusa-config.ts`:
```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+import { Modules } from "@medusajs/framework/utils"
// ...
@@ -28148,18 +28244,14 @@ module.exports = defineConfig({
// ...
modules: [
{
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
+ resolve: "@medusajs/medusa/payment",
options: {
providers: [
- // other providers...
{
- resolve: "@medusajs/medusa/auth-github",
- id: "github",
+ resolve: "@medusajs/medusa/payment-stripe",
+ id: "stripe",
options: {
- clientId: process.env.GITHUB_CLIENT_ID,
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
- callbackUrl: process.env.GITHUB_CALLBACK_URL,
+ apiKey: process.env.STRIPE_API_KEY,
},
},
],
@@ -28173,33 +28265,68 @@ module.exports = defineConfig({
Make sure to add the necessary environment variables for the above options in `.env`:
-```plain
-GITHUB_CLIENT_ID=
-GITHUB_CLIENT_SECRET=
-GITHUB_CALLBACK_URL=
+```bash
+STRIPE_API_KEY=
```
### Module Options
-|Configuration|Description|Required|
-|---|---|---|---|---|
-|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes|
-|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes|
-|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes|
+|Option|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`apiKey\`|A string indicating the Stripe Secret API key.|Yes|-|
+|\`webhookSecret\`|A string indicating the Stripe webhook secret. This is only useful for deployed Medusa applications.|Yes|-|
+|\`capture\`|Whether to automatically capture payment after authorization.|No|\`false\`|
+|\`automatic\_payment\_methods\`|A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you integrate services like Apple pay or Google pay.|No|\`false\`|
+|\`payment\_description\`|A string used as the default description of a payment if none is available in cart.context.payment\_description.|No|-|
***
-## Override Callback URL During Authentication
+## Enable Stripe Providers in a Region
-In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication.
+Before customers can use Stripe to complete their purchases, you must enable the Stripe payment provider(s) in the region where you want to offer this payment method.
-The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md).
+Refer to the [user guide](https://docs.medusajs.com/user-guide/settings/regions#edit-region-details/index.html.md) to learn how to edit a region and enable the Stripe payment provider.
***
-## Examples
+## Setup Stripe Webhooks
-- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments. Refer to [Stripe's documentation](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) on how to setup webhooks.
+
+### Webhook URL
+
+Medusa has a `{server_url}/hooks/payment/{provider_id}` API route that you can use to register webhooks in Stripe, where:
+
+- `{server_url}` is the URL to your deployed Medusa application in server mode.
+- `{provider_id}` is the ID of the provider, such as `stripe_stripe` for basic payments.
+
+The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each:
+
+|Stripe Payment Type|Webhook Endpoint URL|
+|---|---|---|
+|Basic Stripe Payment|\`\{server\_url}/hooks/payment/stripe\_stripe\`|
+|Bancontact Payments|\`\{server\_url}/hooks/payment/stripe-bancontact\_stripe\`|
+|BLIK Payments|\`\{server\_url}/hooks/payment/stripe-blik\_stripe\`|
+|giropay Payments|\`\{server\_url}/hooks/payment/stripe-giropay\_stripe\`|
+|iDEAL Payments|\`\{server\_url}/hooks/payment/stripe-ideal\_stripe\`|
+|Przelewy24 Payments|\`\{server\_url}/hooks/payment/stripe-przelewy24\_stripe\`|
+|PromptPay Payments|\`\{server\_url}/hooks/payment/stripe-promptpay\_stripe\`|
+
+### Webhook Events
+
+When you set up the webhook in Stripe, choose the following events to listen to:
+
+- `payment_intent.amount_capturable_updated`
+- `payment_intent.succeeded`
+- `payment_intent.payment_failed`
+
+***
+
+## Useful Guides
+
+- [Storefront guide: Add Stripe payment method during checkout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/stripe/index.html.md).
+- [Integrate in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter#stripe-integration/index.html.md).
+- [Customize Stripe Integration in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/guides/customize-stripe/index.html.md).
# Get Product Variant Prices using Query
@@ -28467,434 +28594,99 @@ For each product variant, you:
- `priceWithoutTax`: The variant's price without taxes applied.
-# Stripe Module Provider
-
-In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module.
-
-Your technical team must install the Stripe Module Provider in your Medusa application first. Then, refer to [this user guide](https://docs.medusajs.com/user-guide/settings/regions#edit-region-details/index.html.md) to learn how to enable the Stripe payment provider in a region using the Medusa Admin dashboard.
-
-## Register the Stripe Module Provider
-
-### Prerequisites
-
-- [Stripe account](https://stripe.com/)
-- [Stripe Secret API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard)
-- [For deployed Medusa applications, a Stripe webhook secret. Refer to the end of this guide for details on the URL and events.](https://docs.stripe.com/webhooks#add-a-webhook-endpoint)
-
-The Stripe Module Provider is installed by default in your application. To use it, add it to the array of providers passed to the Payment Module in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/payment",
- options: {
- providers: [
- {
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
- options: {
- apiKey: process.env.STRIPE_API_KEY,
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-### Environment Variables
-
-Make sure to add the necessary environment variables for the above options in `.env`:
-
-```bash
-STRIPE_API_KEY=
-```
-
-### Module Options
-
-|Option|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`apiKey\`|A string indicating the Stripe Secret API key.|Yes|-|
-|\`webhookSecret\`|A string indicating the Stripe webhook secret. This is only useful for deployed Medusa applications.|Yes|-|
-|\`capture\`|Whether to automatically capture payment after authorization.|No|\`false\`|
-|\`automatic\_payment\_methods\`|A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you integrate services like Apple pay or Google pay.|No|\`false\`|
-|\`payment\_description\`|A string used as the default description of a payment if none is available in cart.context.payment\_description.|No|-|
-
-***
-
-## Enable Stripe Providers in a Region
-
-Before customers can use Stripe to complete their purchases, you must enable the Stripe payment provider(s) in the region where you want to offer this payment method.
-
-Refer to the [user guide](https://docs.medusajs.com/user-guide/settings/regions#edit-region-details/index.html.md) to learn how to edit a region and enable the Stripe payment provider.
-
-***
-
-## Setup Stripe Webhooks
-
-For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments. Refer to [Stripe's documentation](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) on how to setup webhooks.
-
-### Webhook URL
-
-Medusa has a `{server_url}/hooks/payment/{provider_id}` API route that you can use to register webhooks in Stripe, where:
-
-- `{server_url}` is the URL to your deployed Medusa application in server mode.
-- `{provider_id}` is the ID of the provider, such as `stripe_stripe` for basic payments.
-
-The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each:
-
-|Stripe Payment Type|Webhook Endpoint URL|
-|---|---|---|
-|Basic Stripe Payment|\`\{server\_url}/hooks/payment/stripe\_stripe\`|
-|Bancontact Payments|\`\{server\_url}/hooks/payment/stripe-bancontact\_stripe\`|
-|BLIK Payments|\`\{server\_url}/hooks/payment/stripe-blik\_stripe\`|
-|giropay Payments|\`\{server\_url}/hooks/payment/stripe-giropay\_stripe\`|
-|iDEAL Payments|\`\{server\_url}/hooks/payment/stripe-ideal\_stripe\`|
-|Przelewy24 Payments|\`\{server\_url}/hooks/payment/stripe-przelewy24\_stripe\`|
-|PromptPay Payments|\`\{server\_url}/hooks/payment/stripe-promptpay\_stripe\`|
-
-### Webhook Events
-
-When you set up the webhook in Stripe, choose the following events to listen to:
-
-- `payment_intent.amount_capturable_updated`
-- `payment_intent.succeeded`
-- `payment_intent.payment_failed`
-
-***
-
-## Useful Guides
-
-- [Storefront guide: Add Stripe payment method during checkout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/stripe/index.html.md).
-- [Integrate in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter#stripe-integration/index.html.md).
-- [Customize Stripe Integration in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/guides/customize-stripe/index.html.md).
-
-
## Workflows
+- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md)
- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md)
- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md)
-- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md)
-- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md)
- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md)
-- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md)
-- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md)
-- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md)
-- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
-- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
+- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md)
- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md)
-- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
+- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
+- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md)
- [createCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartCreditLinesWorkflow/index.html.md)
-- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md)
- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md)
+- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md)
- [deleteCartCreditLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCartCreditLinesWorkflow/index.html.md)
- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md)
- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md)
- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md)
- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md)
+- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
+- [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md)
- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md)
- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md)
-- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
- [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md)
-- [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md)
-- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md)
- [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/index.html.md)
-- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md)
+- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md)
+- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md)
+- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md)
+- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md)
+- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
+- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
+- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
+- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md)
+- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md)
- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md)
- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md)
- [createCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomersWorkflow/index.html.md)
-- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
-- [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md)
- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md)
-- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md)
+- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md)
+- [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md)
+- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/index.html.md)
-- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md)
-- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md)
-- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md)
-- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
-- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
+- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md)
- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md)
- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md)
+- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
-- [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/index.html.md)
-- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md)
-- [deleteInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInvitesWorkflow/index.html.md)
-- [refreshInviteTokensWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshInviteTokensWorkflow/index.html.md)
-- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md)
- [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/index.html.md)
-- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md)
+- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md)
- [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md)
-- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
+- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md)
- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
-- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md)
-- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md)
+- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
+- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
+- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
- [deleteServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteServiceZonesWorkflow/index.html.md)
- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md)
- [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/index.html.md)
-- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md)
-- [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md)
-- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md)
- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/index.html.md)
+- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md)
+- [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md)
- [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md)
+- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md)
- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md)
- [bulkCreateDeleteLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/bulkCreateDeleteLevelsWorkflow/index.html.md)
- [createInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryItemsWorkflow/index.html.md)
- [createInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryLevelsWorkflow/index.html.md)
+- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md)
- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md)
- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md)
- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md)
-- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md)
- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md)
- [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/index.html.md)
-- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
-- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
-- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
-- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
-- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md)
-- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md)
-- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
-- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md)
-- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md)
-- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
-- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
-- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
-- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
-- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
-- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md)
-- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
-- [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/index.html.md)
-- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
-- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md)
-- [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/index.html.md)
-- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
-- [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/index.html.md)
-- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/index.html.md)
-- [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/index.html.md)
-- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
-- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md)
-- [cancelOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderTransferRequestWorkflow/index.html.md)
-- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md)
-- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md)
-- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md)
-- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md)
-- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
-- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md)
-- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md)
-- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
-- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
-- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md)
-- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
-- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
-- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
-- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
-- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md)
-- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
-- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
-- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md)
-- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
-- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
-- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
-- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
-- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
-- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
-- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
-- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
-- [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md)
-- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
-- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md)
-- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
-- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
-- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
-- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
-- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
-- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
-- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
-- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md)
-- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md)
-- [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md)
-- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/index.html.md)
-- [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md)
-- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md)
-- [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/index.html.md)
-- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
-- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md)
-- [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md)
-- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
-- [dismissItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestValidationStep/index.html.md)
-- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md)
-- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md)
-- [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md)
-- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
-- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
-- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md)
-- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
-- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
-- [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md)
-- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
-- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
-- [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/index.html.md)
-- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md)
-- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
-- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md)
-- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md)
-- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md)
-- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md)
-- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
-- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
-- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
-- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md)
-- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
-- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md)
-- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md)
-- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
-- [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md)
-- [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md)
-- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
-- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
-- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md)
-- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
-- [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md)
-- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md)
-- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md)
-- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md)
-- [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md)
-- [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md)
-- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
-- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
-- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md)
-- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
-- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md)
-- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md)
-- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
-- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md)
-- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md)
-- [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md)
-- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md)
-- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md)
-- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md)
-- [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/index.html.md)
-- [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md)
-- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md)
-- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md)
-- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md)
-- [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md)
-- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
-- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
-- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md)
-- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md)
-- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md)
-- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md)
-- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
-- [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md)
-- [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md)
-- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md)
-- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md)
-- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md)
-- [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md)
-- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
-- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
-- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md)
-- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md)
-- [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/index.html.md)
-- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md)
-- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md)
-- [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/index.html.md)
-- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
-- [updateReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnWorkflow/index.html.md)
-- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
-- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md)
-- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md)
-- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
-- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md)
-- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md)
-- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md)
-- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md)
+- [deleteInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInvitesWorkflow/index.html.md)
+- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md)
+- [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/index.html.md)
+- [refreshInviteTokensWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshInviteTokensWorkflow/index.html.md)
- [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md)
-- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
-- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md)
-- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
-- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md)
- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
-- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md)
-- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md)
-- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
-- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
-- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md)
-- [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md)
-- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md)
-- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md)
-- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md)
-- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md)
-- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md)
-- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md)
-- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
-- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md)
-- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
-- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
-- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
-- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
-- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md)
-- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md)
-- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md)
-- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md)
-- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md)
-- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md)
-- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md)
-- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md)
-- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md)
-- [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md)
-- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
-- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md)
-- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
-- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
-- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
-- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md)
-- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md)
-- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md)
-- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md)
-- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/index.html.md)
-- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md)
-- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md)
-- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md)
-- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md)
-- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md)
-- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md)
-- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md)
-- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
-- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md)
-- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md)
-- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md)
-- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md)
-- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
-- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
-- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md)
-- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
-- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md)
-- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
-- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md)
-- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md)
-- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md)
-- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md)
-- [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md)
-- [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md)
-- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md)
-- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md)
-- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
+- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
+- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md)
+- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
+- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md)
- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
+- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
+- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
+- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md)
- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md)
-- [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md)
-- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md)
+- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
+- [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md)
- [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md)
- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/index.html.md)
@@ -28904,277 +28696,502 @@ When you set up the webhook in Stripe, choose the following events to listen to:
- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md)
- [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md)
- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md)
-- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
-- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
- [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md)
+- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md)
-- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
+- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md)
-- [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md)
- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md)
+- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
- [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md)
-- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
+- [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md)
+- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md)
+- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md)
+- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md)
+- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
+- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md)
+- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md)
+- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md)
+- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
+- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
+- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md)
+- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md)
+- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
+- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md)
+- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
+- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
+- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
+- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md)
+- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
+- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
+- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md)
+- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
+- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
+- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
+- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
+- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md)
+- [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/index.html.md)
+- [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/index.html.md)
+- [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/index.html.md)
+- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
+- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
+- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md)
+- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/index.html.md)
+- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md)
+- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md)
+- [cancelOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderTransferRequestWorkflow/index.html.md)
+- [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/index.html.md)
+- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
+- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md)
+- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md)
+- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
+- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md)
+- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md)
+- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
+- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
+- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
+- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md)
+- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
+- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
+- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
+- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md)
+- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md)
+- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
+- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
+- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
+- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
+- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
+- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
+- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md)
+- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
+- [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md)
+- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
+- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
+- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
+- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
+- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
+- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
+- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
+- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
+- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md)
+- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
+- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
+- [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md)
+- [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md)
+- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/index.html.md)
+- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md)
+- [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/index.html.md)
+- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md)
+- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md)
+- [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md)
+- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
+- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
+- [dismissItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestValidationStep/index.html.md)
+- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
+- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md)
+- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md)
+- [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md)
+- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
+- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
+- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md)
+- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md)
+- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
+- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
+- [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md)
+- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
+- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
+- [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/index.html.md)
+- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md)
+- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md)
+- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md)
+- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
+- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md)
+- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md)
+- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
+- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
+- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
+- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md)
+- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md)
+- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
+- [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md)
+- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md)
+- [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md)
+- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
+- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
+- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
+- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md)
+- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md)
+- [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md)
+- [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md)
+- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md)
+- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
+- [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md)
+- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
+- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
+- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md)
+- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
+- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md)
+- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md)
+- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md)
+- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md)
+- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md)
+- [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/index.html.md)
+- [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md)
+- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md)
+- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md)
+- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md)
+- [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md)
+- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md)
+- [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md)
+- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md)
+- [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md)
+- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md)
+- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
+- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md)
+- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md)
+- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
+- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md)
+- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
+- [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md)
+- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md)
+- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md)
+- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md)
+- [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md)
+- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md)
+- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
+- [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/index.html.md)
+- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md)
+- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
+- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
+- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
+- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md)
+- [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/index.html.md)
+- [updateReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnWorkflow/index.html.md)
+- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md)
+- [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md)
+- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md)
+- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md)
+- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
+- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md)
+- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md)
+- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
+- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
+- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
+- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md)
+- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md)
+- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md)
+- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
+- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md)
+- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md)
+- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md)
+- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md)
+- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md)
+- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
+- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
+- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md)
+- [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md)
+- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md)
+- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
+- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md)
+- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md)
+- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md)
+- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md)
+- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md)
+- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md)
+- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md)
+- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md)
+- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md)
+- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md)
+- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md)
+- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md)
+- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
+- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
+- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md)
+- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
+- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md)
+- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md)
+- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md)
+- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/index.html.md)
+- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md)
+- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md)
+- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md)
+- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md)
+- [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md)
+- [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md)
+- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md)
+- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
+- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md)
+- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
+- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md)
+- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md)
+- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
+- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md)
+- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md)
+- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md)
+- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md)
+- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
+- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
## Steps
+- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md)
- [createApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/createApiKeysStep/index.html.md)
- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/index.html.md)
-- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md)
-- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md)
- [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md)
+- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md)
- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md)
- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
-- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md)
-- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md)
-- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md)
-- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md)
-- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md)
-- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md)
-- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
-- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md)
- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md)
- [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md)
- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md)
- [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md)
- [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md)
-- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
-- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md)
-- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md)
-- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md)
-- [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md)
-- [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md)
-- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md)
-- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md)
-- [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md)
-- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md)
-- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/index.html.md)
-- [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md)
-- [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md)
-- [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md)
-- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md)
-- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
-- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
-- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md)
-- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
-- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md)
-- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
-- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
-- [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md)
-- [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md)
-- [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md)
-- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md)
-- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md)
-- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md)
-- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
-- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
-- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
-- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
-- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
-- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md)
-- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md)
-- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md)
-- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md)
-- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md)
-- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md)
-- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md)
-- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md)
-- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
-- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md)
-- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md)
-- [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md)
-- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md)
-- [archiveOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/archiveOrdersStep/index.html.md)
-- [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/index.html.md)
-- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
-- [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md)
-- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
-- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
-- [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md)
-- [createOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderChangeStep/index.html.md)
-- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md)
-- [createCompleteReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCompleteReturnStep/index.html.md)
-- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md)
-- [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md)
-- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/index.html.md)
-- [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md)
-- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
-- [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md)
-- [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md)
-- [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md)
-- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md)
-- [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md)
-- [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md)
-- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md)
-- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md)
-- [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md)
-- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
-- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
-- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
-- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
-- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
-- [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md)
-- [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md)
-- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md)
-- [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md)
-- [updateOrderShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderShippingMethodsStep/index.html.md)
-- [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md)
-- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md)
-- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md)
-- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
-- [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md)
-- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
-- [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md)
-- [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/index.html.md)
-- [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md)
-- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md)
-- [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md)
-- [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md)
-- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md)
-- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md)
-- [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md)
-- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md)
-- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md)
-- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
-- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
-- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md)
-- [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md)
-- [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md)
-- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md)
-- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md)
-- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md)
-- [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md)
-- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md)
-- [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md)
-- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md)
-- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md)
-- [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md)
-- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md)
-- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md)
-- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md)
-- [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/index.html.md)
-- [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/index.html.md)
-- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md)
-- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md)
-- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md)
-- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md)
-- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md)
- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
-- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
-- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
+- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
+- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
+- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
+- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md)
+- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md)
+- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md)
+- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md)
+- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md)
+- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md)
+- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md)
+- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
+- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md)
+- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md)
+- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md)
+- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md)
+- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md)
+- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
+- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md)
+- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
+- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
+- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
+- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
+- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md)
+- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md)
+- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md)
+- [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md)
+- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md)
+- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md)
+- [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md)
+- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/index.html.md)
+- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md)
+- [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md)
+- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md)
+- [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md)
+- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
+- [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md)
+- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md)
+- [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md)
+- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
+- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
+- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
+- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
+- [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md)
+- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md)
+- [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md)
+- [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md)
+- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md)
+- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md)
+- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md)
+- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md)
+- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md)
+- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
+- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md)
+- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md)
+- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md)
+- [archiveOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/archiveOrdersStep/index.html.md)
+- [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md)
+- [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md)
+- [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/index.html.md)
+- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
+- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
+- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
+- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md)
+- [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md)
+- [createCompleteReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCompleteReturnStep/index.html.md)
+- [createOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderChangeStep/index.html.md)
+- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/index.html.md)
+- [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md)
+- [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md)
+- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md)
+- [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md)
+- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
+- [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md)
+- [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md)
+- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md)
+- [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md)
+- [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md)
+- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
+- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md)
+- [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md)
+- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
+- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
+- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md)
+- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md)
+- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
+- [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md)
+- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
+- [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md)
+- [updateOrderShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderShippingMethodsStep/index.html.md)
+- [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md)
+- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md)
+- [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md)
+- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md)
+- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md)
+- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md)
+- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md)
+- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md)
+- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md)
+- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md)
+- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md)
+- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md)
+- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md)
+- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
+- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md)
+- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
+- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md)
+- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
+- [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md)
+- [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/index.html.md)
+- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
+- [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md)
+- [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md)
+- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md)
+- [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md)
+- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md)
+- [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md)
+- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md)
+- [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md)
+- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
+- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md)
+- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md)
+- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md)
+- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
+- [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md)
+- [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md)
+- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md)
+- [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md)
+- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md)
+- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md)
+- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md)
+- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md)
+- [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md)
+- [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md)
+- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md)
+- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md)
+- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md)
+- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md)
+- [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/index.html.md)
+- [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/index.html.md)
- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md)
- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
-- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
- [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/index.html.md)
-- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md)
-- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md)
-- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md)
- [removePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/removePriceListPricesStep/index.html.md)
+- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md)
+- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md)
+- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
+- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md)
- [validateVariantPriceLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPriceLinksStep/index.html.md)
-- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md)
+- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md)
+- [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md)
+- [updatePricePreferencesAsArrayStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesAsArrayStep/index.html.md)
+- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md)
+- [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/index.html.md)
+- [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/index.html.md)
+- [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md)
+- [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md)
+- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md)
+- [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/index.html.md)
+- [removeCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeCampaignPromotionsStep/index.html.md)
+- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md)
+- [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md)
+- [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md)
+- [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/index.html.md)
+- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md)
+- [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md)
- [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md)
- [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/index.html.md)
-- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md)
-- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md)
-- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md)
-- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md)
-- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
-- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
-- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md)
-- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md)
-- [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md)
-- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md)
-- [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md)
-- [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/index.html.md)
-- [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md)
-- [removeCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeCampaignPromotionsStep/index.html.md)
-- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md)
-- [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md)
-- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md)
-- [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/index.html.md)
-- [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md)
-- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md)
-- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md)
-- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md)
-- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md)
+- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md)
- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md)
- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md)
- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md)
- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md)
-- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md)
-- [updatePricePreferencesAsArrayStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesAsArrayStep/index.html.md)
-- [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/index.html.md)
-- [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md)
-- [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/index.html.md)
-- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md)
-- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
-- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md)
-- [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/index.html.md)
+- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
+- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
+- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md)
+- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md)
+- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md)
+- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md)
- [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md)
+- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
- [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md)
+- [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/index.html.md)
- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md)
-- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
-- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
-- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
- [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md)
-- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md)
+- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
+- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md)
+- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
- [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md)
- [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md)
-- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md)
- [deleteProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductVariantsStep/index.html.md)
-- [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/index.html.md)
-- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
- [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/index.html.md)
+- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md)
+- [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/index.html.md)
- [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/index.html.md)
+- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
+- [getAllProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getAllProductsStep/index.html.md)
- [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md)
- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md)
+- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md)
- [updateProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductOptionsStep/index.html.md)
- [updateProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTagsStep/index.html.md)
-- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md)
-- [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/index.html.md)
- [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/index.html.md)
-- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
-- [getAllProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getAllProductsStep/index.html.md)
- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md)
+- [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/index.html.md)
+- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
+- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
- [associateLocationsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateLocationsWithSalesChannelsStep/index.html.md)
- [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md)
- [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md)
-- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md)
- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md)
- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md)
+- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md)
- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md)
- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md)
- [updateSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateSalesChannelsStep/index.html.md)
- [listShippingOptionsForContextStep](https://docs.medusajs.com/references/medusa-workflows/steps/listShippingOptionsForContextStep/index.html.md)
-- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
+- [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md)
+- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md)
+- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md)
+- [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md)
+- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md)
+- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md)
+- [deleteUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteUsersStep/index.html.md)
+- [createUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createUsersStep/index.html.md)
+- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md)
- [createTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRateRulesStep/index.html.md)
-- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md)
-- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/index.html.md)
-- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/index.html.md)
- [createTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRegionsStep/index.html.md)
-- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md)
+- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/index.html.md)
- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md)
-- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md)
+- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/index.html.md)
+- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md)
+- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md)
- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md)
- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md)
+- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md)
- [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/index.html.md)
-- [createUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createUsersStep/index.html.md)
-- [deleteUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteUsersStep/index.html.md)
-- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md)
-- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md)
-- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md)
-- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md)
-- [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md)
-- [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md)
-- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md)
# Medusa CLI Reference
@@ -29262,22 +29279,6 @@ By default, the Medusa Admin is built to the `.medusa/server/public/admin` direc
If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
-# develop Command - Medusa CLI Reference
-
-Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
-
-```bash
-npx medusa develop
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -29398,22 +29399,6 @@ npx medusa db:sync-links
|\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.|
-# exec Command - Medusa CLI Reference
-
-Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
-
-```bash
-npx medusa exec [file] [args...]
-```
-
-## Arguments
-
-|Argument|Description|Required|
-|---|---|---|---|---|
-|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
-|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
-
-
# new Command - Medusa CLI Reference
Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
@@ -29443,6 +29428,71 @@ medusa new [ []]
|\`--db-host \\`|The database host to use for database setup.|
+# start Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
+
+
+# develop Command - Medusa CLI Reference
+
+Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
+
+```bash
+npx medusa develop
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
+# telemetry Command - Medusa CLI Reference
+
+Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage.
+
+```bash
+npx medusa telemetry
+```
+
+#### Options
+
+|Option|Description|
+|---|---|---|
+|\`--enable\`|Enable telemetry (default).|
+|\`--disable\`|Disable telemetry.|
+
+
+# exec Command - Medusa CLI Reference
+
+Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
+
+```bash
+npx medusa exec [file] [args...]
+```
+
+## Arguments
+
+|Argument|Description|Required|
+|---|---|---|---|---|
+|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
+|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+
+
# plugin Commands - Medusa CLI Reference
Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
@@ -29504,39 +29554,6 @@ npx medusa plugin:build
```
-# start Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
-
-
-# telemetry Command - Medusa CLI Reference
-
-Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage.
-
-```bash
-npx medusa telemetry
-```
-
-#### Options
-
-|Option|Description|
-|---|---|---|
-|\`--enable\`|Enable telemetry (default).|
-|\`--disable\`|Disable telemetry.|
-
-
# user Command - Medusa CLI Reference
Create a new admin user.
@@ -29641,67 +29658,6 @@ By default, the Medusa Admin is built to the `.medusa/server/public/admin` direc
If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
-# exec Command - Medusa CLI Reference
-
-Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
-
-```bash
-npx medusa exec [file] [args...]
-```
-
-## Arguments
-
-|Argument|Description|Required|
-|---|---|---|---|---|
-|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
-|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
-
-
-# develop Command - Medusa CLI Reference
-
-Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
-
-```bash
-npx medusa develop
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
-# new Command - Medusa CLI Reference
-
-Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
-
-```bash
-medusa new [ []]
-```
-
-## Arguments
-
-|Argument|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-|
-|\`starter\_url\`|The URL of the starter repository to create the project from.|No|\`https://github.com/medusajs/medusa-starter-default\`|
-
-## Options
-
-|Option|Description|
-|---|---|---|
-|\`-y\`|Skip all prompts, such as databaes prompts. A database might not be created if default PostgreSQL credentials don't work.|
-|\`--skip-db\`|Skip database creation.|
-|\`--skip-env\`|Skip populating |
-|\`--db-user \\`|The database user to use for database setup.|
-|\`--db-database \\`|The name of the database used for database setup.|
-|\`--db-pass \\`|The database password to use for database setup.|
-|\`--db-port \\`|The database port to use for database setup.|
-|\`--db-host \\`|The database host to use for database setup.|
-
-
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -29822,6 +29778,67 @@ npx medusa db:sync-links
|\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.|
+# develop Command - Medusa CLI Reference
+
+Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
+
+```bash
+npx medusa develop
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
+# new Command - Medusa CLI Reference
+
+Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
+
+```bash
+medusa new [ []]
+```
+
+## Arguments
+
+|Argument|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-|
+|\`starter\_url\`|The URL of the starter repository to create the project from.|No|\`https://github.com/medusajs/medusa-starter-default\`|
+
+## Options
+
+|Option|Description|
+|---|---|---|
+|\`-y\`|Skip all prompts, such as databaes prompts. A database might not be created if default PostgreSQL credentials don't work.|
+|\`--skip-db\`|Skip database creation.|
+|\`--skip-env\`|Skip populating |
+|\`--db-user \\`|The database user to use for database setup.|
+|\`--db-database \\`|The name of the database used for database setup.|
+|\`--db-pass \\`|The database password to use for database setup.|
+|\`--db-port \\`|The database port to use for database setup.|
+|\`--db-host \\`|The database host to use for database setup.|
+
+
+# exec Command - Medusa CLI Reference
+
+Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
+
+```bash
+npx medusa exec [file] [args...]
+```
+
+## Arguments
+
+|Argument|Description|Required|
+|---|---|---|---|---|
+|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
+|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+
+
# plugin Commands - Medusa CLI Reference
Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
@@ -29883,23 +29900,6 @@ npx medusa plugin:build
```
-# start Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
-
-
# user Command - Medusa CLI Reference
Create a new admin user.
@@ -29935,6 +29935,23 @@ npx medusa telemetry
|\`--disable\`|Disable telemetry.|
+# start Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
+
+
# Medusa JS SDK
In this documentation, you'll learn how to install and use Medusa's JS SDK.
@@ -34385,6 +34402,795 @@ const user = await userModuleService.createUsers({
```
+# Implement Custom Line Item Pricing in Medusa
+
+In this guide, you'll learn how to add line items with custom prices to a cart in Medusa.
+
+When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. The Medusa application's commerce features are built around [commerce modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md) which are available out-of-the-box. These features include managing carts and adding line items to them.
+
+By default, you can add product variants to the cart, where the price of its associated line item is based on the product variant's price. However, you can build customizations to add line items with custom prices to the cart. This is useful when integrating an Enterprise Resource Planning (ERP), Product Information Management (PIM), or other third-party services that provide real-time prices for your products.
+
+To showcase how to add line items with custom prices to the cart, this guide uses [GoldAPI.io](https://www.goldapi.io) as an example of a third-party system that you can integrate for real-time prices. You can follow the same approach for other third-party integrations that provide custom pricing.
+
+You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
+
+### Summary
+
+This guide will teach you how to:
+
+- Install and set up Medusa.
+- Integrate the third-party service [GoldAPI.io](https://www.goldapi.io) that retrieves real-time prices for metals like Gold and Silver.
+- Add an API route to add a product variant that has metals, such as a gold ring, to the cart with the real-time price retrieved from the third-party service.
+
+
+
+- [Custom Item Price Repository](https://github.com/medusajs/examples/tree/main/custom-item-price): Find the full code for this guide in this repository.
+- [OpenApi Specs for Postman](https://res.cloudinary.com/dza7lstvk/raw/upload/v1738246728/OpenApi/Custom_Item_Price_gdfnl3.yaml): Import this OpenApi Specs file into tools like Postman.
+
+***
+
+## Step 1: Install a Medusa Application
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+- [PostgreSQL](https://www.postgresql.org/download/)
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+You'll first be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
+
+Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
+
+Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
+
+***
+
+## Step 2: Integrate GoldAPI.io
+
+### Prerequisites
+
+- [GoldAPI.io Account. You can create a free account.](https://www.goldapi.io)
+
+To integrate third-party services into Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
+
+In this step, you'll create a Metal Price Module that uses the GoldAPI.io service to retrieve real-time prices for metals like Gold and Silver. You'll use this module later to retrieve the real-time price of a product variant based on the metals in it, and add it to the cart with that custom price.
+
+Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
+
+### Create Module Directory
+
+A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/metal-prices`.
+
+
+
+### Create Module's Service
+
+You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
+
+In this section, you'll create the Metal Prices Module's service that connects to the GoldAPI.io service to retrieve real-time prices for metals.
+
+Start by creating the file `src/modules/metal-prices/service.ts` with the following content:
+
+
+
+```ts title="src/modules/metal-prices/service.ts"
+type Options = {
+ accessToken: string
+ sandbox?: boolean
+}
+
+export default class MetalPricesModuleService {
+ protected options_: Options
+
+ constructor({}, options: Options) {
+ this.options_ = options
+ }
+}
+```
+
+A module can accept options that are passed to its service. You define an `Options` type that indicates the options the module accepts. It accepts two options:
+
+- `accessToken`: The access token for the GoldAPI.io service.
+- `sandbox`: A boolean that indicates whether to simulate sending requests to the GoldAPI.io service. This is useful when running in a test environment.
+
+The service's constructor receives the module's options as a second parameter. You store the options in the service's `options_` property.
+
+A module has a container of Medusa framework tools and local resources in the module that you can access in the service constructor's first parameter. Learn more in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
+
+#### Add Method to Retrieve Metal Prices
+
+Next, you'll add the method to retrieve the metal prices from the third-party service.
+
+First, add the following types at the beginning of `src/modules/metal-prices/service.ts`:
+
+```ts title="src/modules/metal-prices/service.ts"
+export enum MetalSymbols {
+ Gold = "XAU",
+ Silver = "XAG",
+ Platinum = "XPT",
+ Palladium = "XPD"
+}
+
+export type PriceResponse = {
+ metal: MetalSymbols
+ currency: string
+ exchange: string
+ symbol: string
+ price: number
+ [key: string]: unknown
+}
+
+```
+
+The `MetalSymbols` enum defines the symbols for metals like Gold, Silver, Platinum, and Palladium. The `PriceResponse` type defines the structure of the response from the GoldAPI.io's endpoint.
+
+Next, add the method `getMetalPrices` to the `MetalPricesModuleService` class:
+
+```ts title="src/modules/metal-prices/service.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+
+// ...
+
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalPrice(
+ symbol: MetalSymbols,
+ currency: string
+ ): Promise {
+ const upperCaseSymbol = symbol.toUpperCase()
+ const upperCaseCurrency = currency.toUpperCase()
+
+ return fetch(`https://www.goldapi.io/api/${upperCaseSymbol}/${upperCaseCurrency}`, {
+ headers: {
+ "x-access-token": this.options_.accessToken,
+ "Content-Type": "application/json",
+ },
+ redirect: "follow",
+ }).then((response) => response.json())
+ .then((response) => {
+ if (response.error) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ response.error
+ )
+ }
+
+ return response
+ })
+ }
+}
+```
+
+The `getMetalPrice` method accepts the metal symbol and currency as parameters. You send a request to GoldAPI.io's `/api/{symbol}/{currency}` endpoint to retrieve the metal's price, also passing the access token in the request's headers.
+
+If the response contains an error, you throw a `MedusaError` with the error message. Otherwise, you return the response, which is of type `PriceResponse`.
+
+#### Add Helper Methods
+
+You'll also add two helper methods to the `MetalPricesModuleService`. The first one is `getMetalSymbols` that returns the metal symbols as an array of strings:
+
+```ts title="src/modules/metal-prices/service.ts"
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalSymbols(): Promise {
+ return Object.values(MetalSymbols)
+ }
+}
+```
+
+The second is `getMetalSymbol` that receives a name like `gold` and returns the corresponding metal symbol:
+
+```ts title="src/modules/metal-prices/service.ts"
+export default class MetalPricesModuleService {
+ // ...
+ async getMetalSymbol(name: string): Promise {
+ const formattedName = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
+ return MetalSymbols[formattedName as keyof typeof MetalSymbols]
+ }
+}
+```
+
+You'll use these methods in later steps.
+
+### Export Module Definition
+
+The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
+
+So, create the file `src/modules/metal-prices/index.ts` with the following content:
+
+
+
+```ts title="src/modules/metal-prices/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import MetalPricesModuleService from "./service"
+
+export const METAL_PRICES_MODULE = "metal-prices"
+
+export default Module(METAL_PRICES_MODULE, {
+ service: MetalPricesModuleService,
+})
+```
+
+You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
+
+1. The module's name, which is `metal-prices`.
+2. An object with a required property `service` indicating the module's service.
+
+### Add Module to Medusa's Configurations
+
+Once you finish building the module, add it to Medusa's configurations to start using it.
+
+In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/metal-prices",
+ options: {
+ accessToken: process.env.GOLD_API_TOKEN,
+ sandbox: process.env.GOLD_API_SANDBOX === "true",
+ },
+ },
+ ],
+})
+```
+
+Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
+
+The object also has an `options` property that accepts the module's options. You set the `accessToken` and `sandbox` options based on environment variables.
+
+You'll find the access token at the top of your GoldAPI.io dashboard.
+
+
+
+Set the access token as an environment variable in `.env`:
+
+```bash
+GOLD_API_TOKEN=
+```
+
+You'll start using the module in the next steps.
+
+***
+
+## Step 3: Add Custom Item to Cart Workflow
+
+In this section, you'll implement the logic to retrieve the real-time price of a variant based on the metals in it, then add the variant to the cart with the custom price. You'll implement this logic in a workflow.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an endpoint.
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
+
+The workflow you'll implement in this section has the following steps:
+
+- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the cart's ID and currency using Query.
+- [useQueryGraphStep (Retrieve Variant)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the variant's details using Query
+- [getVariantMetalPricesStep](#getvariantmetalpricesstep): Retrieve the variant's price using the third-party service.
+- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md): Add the item with the custom price to the cart.
+- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the updated cart's details using Query.
+
+`useQueryGraphStep` and `addToCartWorkflow` are available through Medusa's core workflows package. You'll only implement the `getVariantMetalPricesStep`.
+
+### getVariantMetalPricesStep
+
+The `getVariantMetalPricesStep` will retrieve the real-time metal price of a variant received as an input.
+
+To create the step, create the file `src/workflows/steps/get-variant-metal-prices.ts` with the following content:
+
+
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+import { ProductVariantDTO } from "@medusajs/framework/types"
+import { METAL_PRICES_MODULE } from "../../modules/metal-prices"
+import MetalPricesModuleService from "../../modules/metal-prices/service"
+
+export type GetVariantMetalPricesStepInput = {
+ variant: ProductVariantDTO & {
+ calculated_price?: {
+ calculated_amount: number
+ }
+ }
+ currencyCode: string
+ quantity?: number
+}
+
+export const getVariantMetalPricesStep = createStep(
+ "get-variant-metal-prices",
+ async ({
+ variant,
+ currencyCode,
+ quantity = 1,
+ }: GetVariantMetalPricesStepInput, { container }) => {
+ const metalPricesModuleService: MetalPricesModuleService =
+ container.resolve(METAL_PRICES_MODULE)
+
+ // TODO
+ }
+)
+```
+
+You create a step with `createStep` from the Workflows SDK. It accepts two parameters:
+
+1. The step's unique name, which is `get-variant-metal-prices`.
+2. An async function that receives two parameters:
+ - An input object with the variant, currency code, and quantity. The variant has a `calculated_price` property that holds the variant's fixed price in the Medusa application. This is useful when you want to add a fixed price to the real-time custom price, such as handling fees.
+ - The [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools that you can access in the step.
+
+In the step function, so far you only resolve the Metal Prices Module's service from the Medusa container.
+
+Next, you'll validate that the specified variant can have its price calculated. Add the following import at the top of the file:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+```
+
+And replace the `TODO` in the step function with the following:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+const variantMetal = variant.options.find(
+ (option) => option.option?.title === "Metal"
+)?.value
+const metalSymbol = await metalPricesModuleService
+ .getMetalSymbol(variantMetal || "")
+
+if (!metalSymbol) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Variant doesn't have metal. Make sure the variant's SKU matches a metal symbol."
+ )
+}
+
+if (!variant.weight) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Variant doesn't have weight. Make sure the variant has weight to calculate its price."
+ )
+}
+
+// TODO retrieve custom price
+```
+
+In the code above, you first retrieve the metal option's value from the variant's options, assuming that a variant has metals if it has a `Metal` option. Then, you retrieve the metal symbol of the option's value using the `getMetalSymbol` method of the Metal Prices Module's service.
+
+If the variant doesn't have a metal in its options, the option's value is not valid, or the variant doesn't have a weight, you throw an error. The weight is necessary to calculate the price based on the metal's price per weight.
+
+Next, you'll retrieve the real-time price of the metal using the third-party service. Replace the `TODO` with the following:
+
+```ts title="src/workflows/steps/get-variant-metal-prices.ts"
+let price = variant.calculated_price?.calculated_amount || 0
+const weight = variant.weight
+const { price: metalPrice } = await metalPricesModuleService.getMetalPrice(
+ metalSymbol as MetalSymbols, currencyCode
+)
+price += (metalPrice * weight * quantity)
+
+return new StepResponse(price)
+```
+
+In the code above, you first set the price to the variant's fixed price, if it has one. Then, you retrieve the metal's price using the `getMetalPrice` method of the Metal Prices Module's service.
+
+Finally, you calculate the price by multiplying the metal's price by the variant's weight and the quantity to add to the cart, then add the fixed price to it.
+
+Every step must return a `StepResponse` instance. The `StepResponse` constructor accepts the step's output as a parameter, which in this case is the variant's price.
+
+### Create addCustomToCartWorkflow
+
+Now that you have the `getVariantMetalPricesStep`, you can create the workflow that adds the item with custom pricing to the cart.
+
+Create the file `src/workflows/add-custom-to-cart.ts` with the following content:
+
+
+
+```ts title="src/workflows/add-custom-to-cart.ts" highlights={workflowHighlights}
+import { createWorkflow } from "@medusajs/framework/workflows-sdk"
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+import { QueryContext } from "@medusajs/framework/utils"
+
+type AddCustomToCartWorkflowInput = {
+ cart_id: string
+ item: {
+ variant_id: string
+ quantity: number
+ metadata?: Record
+ }
+}
+
+export const addCustomToCartWorkflow = createWorkflow(
+ "add-custom-to-cart",
+ ({ cart_id, item }: AddCustomToCartWorkflowInput) => {
+ // @ts-ignore
+ const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ filters: { id: cart_id },
+ fields: ["id", "currency_code"],
+ })
+
+ const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "*",
+ "options.*",
+ "options.option.*",
+ "calculated_price.*",
+ ],
+ filters: {
+ id: item.variant_id,
+ },
+ options: {
+ throwIfKeyNotFound: true,
+ },
+ context: {
+ calculated_price: QueryContext({
+ currency_code: carts[0].currency_code,
+ }),
+ },
+ }).config({ name: "retrieve-variant" })
+
+ // TODO add more steps
+ }
+)
+```
+
+You create a workflow with `createWorkflow` from the Workflows SDK. It accepts two parameters:
+
+1. The workflow's unique name, which is `add-custom-to-cart`.
+2. A function that receives an input object with the cart's ID and the item to add to the cart. The item has the variant's ID, quantity, and optional metadata.
+
+In the function, you first retrieve the cart's details using the `useQueryGraphStep` helper step. This step uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) which is a Modules SDK tool that retrieves data across modules. You use it to retrieve the cart's ID and currency code.
+
+You also retrieve the variant's details using the `useQueryGraphStep` helper step. You pass the variant's ID to the step's filters and specify the fields to retrieve. To retrieve the variant's price based on the cart's context, you pass the cart's currency code to the `calculated_price` context.
+
+Next, you'll retrieve the variant's real-time price using the `getVariantMetalPricesStep` you created earlier. First, add the following import:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import {
+ getVariantMetalPricesStep,
+ GetVariantMetalPricesStepInput,
+} from "./steps/get-variant-metal-prices"
+```
+
+Then, replace the `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+const price = getVariantMetalPricesStep({
+ variant: variants[0],
+ currencyCode: carts[0].currency_code,
+ quantity: item.quantity,
+} as unknown as GetVariantMetalPricesStepInput)
+
+// TODO add item with custom price to cart
+```
+
+You execute the `getVariantMetalPricesStep` passing it the variant's details, the cart's currency code, and the quantity of the item to add to the cart. The step returns the variant's custom price.
+
+Next, you'll add the item with the custom price to the cart. First, add the following imports at the top of the file:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import { transform } from "@medusajs/framework/workflows-sdk"
+import { addToCartWorkflow } from "@medusajs/medusa/core-flows"
+```
+
+Then, replace the `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+const itemToAdd = transform({
+ item,
+ price,
+}, (data) => {
+ return [{
+ ...data.item,
+ unit_price: data.price,
+ }]
+})
+
+addToCartWorkflow.runAsStep({
+ input: {
+ items: itemToAdd,
+ cart_id,
+ },
+})
+
+// TODO retrieve and return cart
+```
+
+You prepare the item to add to the cart using `transform` from the Workflows SDK. It allows you to manipulate and create variables in a workflow. After that, you use Medusa's `addToCartWorkflow` to add the item with the custom price to the cart.
+
+A workflow's constructor function has some constraints in implementation, which is why you need to use `transform` for variable manipulation. Learn more about these constraints in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
+
+Lastly, you'll retrieve the cart's details again and return them. Add the following import at the beginning of the file:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+import { WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+```
+
+And replace the last `TODO` in the workflow with the following:
+
+```ts title="src/workflows/add-custom-to-cart.ts"
+// @ts-ignore
+const { data: updatedCarts } = useQueryGraphStep({
+ entity: "cart",
+ filters: { id: cart_id },
+ fields: ["id", "items.*"],
+}).config({ name: "refetch-cart" })
+
+return new WorkflowResponse({
+ cart: updatedCarts[0],
+})
+```
+
+In the code above, you retrieve the updated cart's details using the `useQueryGraphStep` helper step. To return data from the workflow, you create and return a `WorkflowResponse` instance. It accepts as a parameter the data to return, which is the updated cart.
+
+In the next step, you'll use the workflow in a custom route to add an item with a custom price to the cart.
+
+***
+
+## Step 4: Create Add Custom Item to Cart API Route
+
+Now that you've implemented the logic to add an item with a custom price to the cart, you'll expose this functionality in an API route.
+
+An API Route is an endpoint that exposes commerce features to external applications and clients, such as storefronts. You'll create an API route at the path `/store/carts/:id/line-items-metals` that executes the workflow from the previous step to add a product variant with custom price to the cart.
+
+Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
+
+### Create API Route
+
+An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory.
+
+The path of the API route is the file's path relative to `src/api`. So, to create the `/store/carts/:id/line-items-metals` API route, create the file `src/api/store/carts/[id]/line-items-metals/route.ts` with the following content:
+
+
+
+```ts title="src/api/store/carts/[id]/line-items-metals/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { HttpTypes } from "@medusajs/framework/types"
+import { addCustomToCartWorkflow } from "../../../../../workflows/add-custom-to-cart"
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const { id } = req.params
+ const item = req.validatedBody
+
+ const { result } = await addCustomToCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: id,
+ item,
+ },
+ })
+
+ res.status(200).json({ cart: result.cart })
+}
+```
+
+Since you export a `POST` function in this file, you're exposing a `POST` API route at `/store/carts/:id/line-items-metals`. The route handler function accepts two parameters:
+
+1. A request object with details and context on the request, such as path and body parameters.
+2. A response object to manipulate and send the response.
+
+In the function, you retrieve the cart's ID from the path parameter, and the item's details from the request body. This API route will accept the same request body parameters as Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
+
+Then, you execute the `addCustomToCartWorkflow` by invoking it, passing it the Medusa container, which is available in the request's `scope` property, then executing its `run` method. You pass the workflow's input object with the cart's ID and the item to add to the cart.
+
+Finally, you return a response with the updated cart's details.
+
+### Add Request Body Validation Middleware
+
+To ensure that the request body contains the required parameters, you'll add a middleware that validates the incoming request's body based on a defined schema.
+
+A middleware is a function executed before the API route when a request is sent to it. You define middlewares in Medusa in the `src/api/middlewares.ts` directory.
+
+Learn more about middlewares in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md).
+
+To add a validation middleware to the custom API route, create the file `src/api/middlewares.ts` with the following content:
+
+
+
+```ts title="src/api/middlewares.ts"
+import {
+ defineMiddlewares,
+ validateAndTransformBody,
+} from "@medusajs/framework/http"
+import {
+ StoreAddCartLineItem,
+} from "@medusajs/medusa/api/store/carts/validators"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/store/carts/:id/line-items-metals",
+ method: "POST",
+ middlewares: [
+ validateAndTransformBody(
+ StoreAddCartLineItem
+ ),
+ ],
+ },
+ ],
+})
+```
+
+In this file, you export the middlewares definition using `defineMiddlewares` from the Medusa Framework. This function accepts an object having a `routes` property, which is an array of middleware configurations to apply on routes.
+
+You pass in the `routes` array an object having the following properties:
+
+- `matcher`: The route to apply the middleware on.
+- `method`: The HTTP method to apply the middleware on for the specified API route.
+- `middlewares`: An array of the middlewares to apply. You apply the `validateAndTransformBody` middleware, which validates the request body based on the `StoreAddCartLineItem` schema. This validation schema is the same schema used for Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
+
+Any request sent to the `/store/carts/:id/line-items-metals` API route will now fail if it doesn't have the required parameters.
+
+Learn more about API route validation in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/validation/index.html.md).
+
+### Prepare to Test API Route
+
+Before you test the API route, you'll prepare and retrieve the necessary data to add a product variant with a custom price to the cart.
+
+#### Create Product with Metal Variant
+
+You'll first create a product that has a `Metal` option, and variant(s) with values for this option.
+
+Start the Medusa application with the following command:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, open the Medusa Admin dashboard at `localhost:9000/app` and log in with the email and password you created when you installed the Medusa application in the first step.
+
+Once you log in, click on Products in the sidebar, then click the Create button at the top right.
+
+
+
+Then, in the Create Product form:
+
+1. Enter a name for the product, and optionally enter other details like description.
+2. Enable the "Yes, this is a product with variants" toggle.
+3. Under Product Options, enter "Metal" for the title, and enter "Gold" for the values.
+
+Once you're done, click the Continue button.
+
+
+
+You can skip the next two steps by clicking the Continue button again, then the Publish button.
+
+Once you're done, the product's page will open. You'll now add weight to the product's Gold variant. To do that:
+
+- Scroll to the Variants section and find the Gold variant.
+- Click on the three-dots icon at its right.
+- Choose "Edit" from the dropdown.
+
+
+
+In the side window that opens, find the Weight field, enter the weight, and click the Save button.
+
+
+
+Finally, you need to set fixed prices for the variant, even if they're just `0`. To do that:
+
+1. Click on the three-dots icon at the top right of the Variants section.
+2. Choose "Edit Prices" from the dropdown.
+
+
+
+For each cell in the table, either enter a fixed price for the specified currency or leave it as `0`. Once you're done, click the Save button.
+
+
+
+You'll use this variant to add it to the cart later. You can find its ID by clicking on the variant, opening its details page. Then, on the details page, click on the icon at the right of the JSON section, and copy the ID from the JSON data.
+
+
+
+#### Retrieve Publishable API Key
+
+All requests sent to API routes starting with `/store` must have a publishable API key in the header. This ensures the request's operations are scoped to the publishable API key's associated sales channels. For example, products that aren't available in a cart's sales channel can't be added to it.
+
+To retrieve the publishable API key, on the Medusa Admin:
+
+1. Click on Settings in the sidebar at the bottom left.
+2. Click on Publishable API Keys from the sidebar, then click on a publishable API key in the list.
+
+
+
+3. Click on the publishable API key to copy it.
+
+
+
+You'll use this key when you test the API route.
+
+### Test API Route
+
+To test out the API route, you need to create a cart. A cart must be associated with a region. So, to retrieve the ID of a region in your store, send a `GET` request to the `/store/regions` API route:
+
+```bash
+curl 'localhost:9000/store/regions' \
+-H 'x-publishable-api-key: {api_key}'
+```
+
+Make sure to replace `{api_key}` with the publishable API key you copied earlier.
+
+This will return a list of regions. Copy the ID of one of the regions.
+
+Then, send a `POST` request to the `/store/carts` API route to create a cart:
+
+```bash
+curl -X POST 'localhost:9000/store/carts' \
+-H 'x-publishable-api-key: {api_key}' \
+-H 'Content-Type: application/json' \
+--data '{
+ "region_id": "{region_id}"
+}'
+```
+
+Make sure to replace `{api_key}` with the publishable API key you copied earlier, and `{region_id}` with the ID of a region from the previous request.
+
+This will return the created cart. Copy the ID of the cart to use it next.
+
+Finally, to add the Gold variant to the cart with a custom price, send a `POST` request to the `/store/carts/:id/line-items-metals` API route:
+
+```bash
+curl -X POST 'localhost:9000/store/carts/{cart_id}/line-items-metals' \
+-H 'x-publishable-api-key: {api_key}' \
+-H 'Content-Type: application/json' \
+--data '{
+ "variant_id": "{variant_id}",
+ "quantity": 1
+}'
+```
+
+Make sure to replace:
+
+- `{api_key}` with the publishable API key you copied earlier.
+- `{cart_id}` with the ID of the cart you created.
+- `{variant_id}` with the ID of the Gold variant you created.
+
+This will return the cart's details, where you can see in its `items` array the item with the custom price:
+
+```json title="Example Response"
+{
+ "cart": {
+ "items": [
+ {
+ "variant_id": "{variant_id}",
+ "quantity": 1,
+ "is_custom_price": true,
+ // example custom price
+ "unit_price": 2000
+ }
+ ]
+ }
+}
+```
+
+The price will be the result of the calculation you've implemented earlier, which is the fixed price of the variant plus the real-time price of the metal, multiplied by the weight of the variant and the quantity added to the cart.
+
+This price will be reflected in the cart's total price, and you can proceed to checkout with the custom-priced item.
+
+***
+
+## Next Steps
+
+You've now implemented custom item pricing in Medusa. You can also customize the [storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md) to use the new API route to add custom-priced items to the cart.
+
+If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+
+
# Implement Quote Management in Medusa
In this guide, you'll learn how to implement quote management in Medusa.
@@ -38266,795 +39072,6 @@ If you're new to Medusa, check out the [main documentation](https://docs.medusaj
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-# Implement Custom Line Item Pricing in Medusa
-
-In this guide, you'll learn how to add line items with custom prices to a cart in Medusa.
-
-When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. The Medusa application's commerce features are built around [commerce modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md) which are available out-of-the-box. These features include managing carts and adding line items to them.
-
-By default, you can add product variants to the cart, where the price of its associated line item is based on the product variant's price. However, you can build customizations to add line items with custom prices to the cart. This is useful when integrating an Enterprise Resource Planning (ERP), Product Information Management (PIM), or other third-party services that provide real-time prices for your products.
-
-To showcase how to add line items with custom prices to the cart, this guide uses [GoldAPI.io](https://www.goldapi.io) as an example of a third-party system that you can integrate for real-time prices. You can follow the same approach for other third-party integrations that provide custom pricing.
-
-You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
-
-### Summary
-
-This guide will teach you how to:
-
-- Install and set up Medusa.
-- Integrate the third-party service [GoldAPI.io](https://www.goldapi.io) that retrieves real-time prices for metals like Gold and Silver.
-- Add an API route to add a product variant that has metals, such as a gold ring, to the cart with the real-time price retrieved from the third-party service.
-
-
-
-- [Custom Item Price Repository](https://github.com/medusajs/examples/tree/main/custom-item-price): Find the full code for this guide in this repository.
-- [OpenApi Specs for Postman](https://res.cloudinary.com/dza7lstvk/raw/upload/v1738246728/OpenApi/Custom_Item_Price_gdfnl3.yaml): Import this OpenApi Specs file into tools like Postman.
-
-***
-
-## Step 1: Install a Medusa Application
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-- [PostgreSQL](https://www.postgresql.org/download/)
-
-Start by installing the Medusa application on your machine with the following command:
-
-```bash
-npx create-medusa-app@latest
-```
-
-You'll first be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
-
-Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
-
-The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
-
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
-
-Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
-
-***
-
-## Step 2: Integrate GoldAPI.io
-
-### Prerequisites
-
-- [GoldAPI.io Account. You can create a free account.](https://www.goldapi.io)
-
-To integrate third-party services into Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
-
-In this step, you'll create a Metal Price Module that uses the GoldAPI.io service to retrieve real-time prices for metals like Gold and Silver. You'll use this module later to retrieve the real-time price of a product variant based on the metals in it, and add it to the cart with that custom price.
-
-Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
-
-### Create Module Directory
-
-A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/metal-prices`.
-
-
-
-### Create Module's Service
-
-You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
-
-In this section, you'll create the Metal Prices Module's service that connects to the GoldAPI.io service to retrieve real-time prices for metals.
-
-Start by creating the file `src/modules/metal-prices/service.ts` with the following content:
-
-
-
-```ts title="src/modules/metal-prices/service.ts"
-type Options = {
- accessToken: string
- sandbox?: boolean
-}
-
-export default class MetalPricesModuleService {
- protected options_: Options
-
- constructor({}, options: Options) {
- this.options_ = options
- }
-}
-```
-
-A module can accept options that are passed to its service. You define an `Options` type that indicates the options the module accepts. It accepts two options:
-
-- `accessToken`: The access token for the GoldAPI.io service.
-- `sandbox`: A boolean that indicates whether to simulate sending requests to the GoldAPI.io service. This is useful when running in a test environment.
-
-The service's constructor receives the module's options as a second parameter. You store the options in the service's `options_` property.
-
-A module has a container of Medusa framework tools and local resources in the module that you can access in the service constructor's first parameter. Learn more in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md).
-
-#### Add Method to Retrieve Metal Prices
-
-Next, you'll add the method to retrieve the metal prices from the third-party service.
-
-First, add the following types at the beginning of `src/modules/metal-prices/service.ts`:
-
-```ts title="src/modules/metal-prices/service.ts"
-export enum MetalSymbols {
- Gold = "XAU",
- Silver = "XAG",
- Platinum = "XPT",
- Palladium = "XPD"
-}
-
-export type PriceResponse = {
- metal: MetalSymbols
- currency: string
- exchange: string
- symbol: string
- price: number
- [key: string]: unknown
-}
-
-```
-
-The `MetalSymbols` enum defines the symbols for metals like Gold, Silver, Platinum, and Palladium. The `PriceResponse` type defines the structure of the response from the GoldAPI.io's endpoint.
-
-Next, add the method `getMetalPrices` to the `MetalPricesModuleService` class:
-
-```ts title="src/modules/metal-prices/service.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-
-// ...
-
-export default class MetalPricesModuleService {
- // ...
- async getMetalPrice(
- symbol: MetalSymbols,
- currency: string
- ): Promise {
- const upperCaseSymbol = symbol.toUpperCase()
- const upperCaseCurrency = currency.toUpperCase()
-
- return fetch(`https://www.goldapi.io/api/${upperCaseSymbol}/${upperCaseCurrency}`, {
- headers: {
- "x-access-token": this.options_.accessToken,
- "Content-Type": "application/json",
- },
- redirect: "follow",
- }).then((response) => response.json())
- .then((response) => {
- if (response.error) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- response.error
- )
- }
-
- return response
- })
- }
-}
-```
-
-The `getMetalPrice` method accepts the metal symbol and currency as parameters. You send a request to GoldAPI.io's `/api/{symbol}/{currency}` endpoint to retrieve the metal's price, also passing the access token in the request's headers.
-
-If the response contains an error, you throw a `MedusaError` with the error message. Otherwise, you return the response, which is of type `PriceResponse`.
-
-#### Add Helper Methods
-
-You'll also add two helper methods to the `MetalPricesModuleService`. The first one is `getMetalSymbols` that returns the metal symbols as an array of strings:
-
-```ts title="src/modules/metal-prices/service.ts"
-export default class MetalPricesModuleService {
- // ...
- async getMetalSymbols(): Promise {
- return Object.values(MetalSymbols)
- }
-}
-```
-
-The second is `getMetalSymbol` that receives a name like `gold` and returns the corresponding metal symbol:
-
-```ts title="src/modules/metal-prices/service.ts"
-export default class MetalPricesModuleService {
- // ...
- async getMetalSymbol(name: string): Promise {
- const formattedName = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
- return MetalSymbols[formattedName as keyof typeof MetalSymbols]
- }
-}
-```
-
-You'll use these methods in later steps.
-
-### Export Module Definition
-
-The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
-
-So, create the file `src/modules/metal-prices/index.ts` with the following content:
-
-
-
-```ts title="src/modules/metal-prices/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import MetalPricesModuleService from "./service"
-
-export const METAL_PRICES_MODULE = "metal-prices"
-
-export default Module(METAL_PRICES_MODULE, {
- service: MetalPricesModuleService,
-})
-```
-
-You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
-
-1. The module's name, which is `metal-prices`.
-2. An object with a required property `service` indicating the module's service.
-
-### Add Module to Medusa's Configurations
-
-Once you finish building the module, add it to Medusa's configurations to start using it.
-
-In `medusa-config.ts`, add a `modules` property and pass an array with your custom module:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/metal-prices",
- options: {
- accessToken: process.env.GOLD_API_TOKEN,
- sandbox: process.env.GOLD_API_SANDBOX === "true",
- },
- },
- ],
-})
-```
-
-Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name.
-
-The object also has an `options` property that accepts the module's options. You set the `accessToken` and `sandbox` options based on environment variables.
-
-You'll find the access token at the top of your GoldAPI.io dashboard.
-
-
-
-Set the access token as an environment variable in `.env`:
-
-```bash
-GOLD_API_TOKEN=
-```
-
-You'll start using the module in the next steps.
-
-***
-
-## Step 3: Add Custom Item to Cart Workflow
-
-In this section, you'll implement the logic to retrieve the real-time price of a variant based on the metals in it, then add the variant to the cart with the custom price. You'll implement this logic in a workflow.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an endpoint.
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
-
-The workflow you'll implement in this section has the following steps:
-
-- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the cart's ID and currency using Query.
-- [useQueryGraphStep (Retrieve Variant)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the variant's details using Query
-- [getVariantMetalPricesStep](#getvariantmetalpricesstep): Retrieve the variant's price using the third-party service.
-- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md): Add the item with the custom price to the cart.
-- [useQueryGraphStep (Retrieve Cart)](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve the updated cart's details using Query.
-
-`useQueryGraphStep` and `addToCartWorkflow` are available through Medusa's core workflows package. You'll only implement the `getVariantMetalPricesStep`.
-
-### getVariantMetalPricesStep
-
-The `getVariantMetalPricesStep` will retrieve the real-time metal price of a variant received as an input.
-
-To create the step, create the file `src/workflows/steps/get-variant-metal-prices.ts` with the following content:
-
-
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-import { createStep } from "@medusajs/framework/workflows-sdk"
-import { ProductVariantDTO } from "@medusajs/framework/types"
-import { METAL_PRICES_MODULE } from "../../modules/metal-prices"
-import MetalPricesModuleService from "../../modules/metal-prices/service"
-
-export type GetVariantMetalPricesStepInput = {
- variant: ProductVariantDTO & {
- calculated_price?: {
- calculated_amount: number
- }
- }
- currencyCode: string
- quantity?: number
-}
-
-export const getVariantMetalPricesStep = createStep(
- "get-variant-metal-prices",
- async ({
- variant,
- currencyCode,
- quantity = 1,
- }: GetVariantMetalPricesStepInput, { container }) => {
- const metalPricesModuleService: MetalPricesModuleService =
- container.resolve(METAL_PRICES_MODULE)
-
- // TODO
- }
-)
-```
-
-You create a step with `createStep` from the Workflows SDK. It accepts two parameters:
-
-1. The step's unique name, which is `get-variant-metal-prices`.
-2. An async function that receives two parameters:
- - An input object with the variant, currency code, and quantity. The variant has a `calculated_price` property that holds the variant's fixed price in the Medusa application. This is useful when you want to add a fixed price to the real-time custom price, such as handling fees.
- - The [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools that you can access in the step.
-
-In the step function, so far you only resolve the Metal Prices Module's service from the Medusa container.
-
-Next, you'll validate that the specified variant can have its price calculated. Add the following import at the top of the file:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-```
-
-And replace the `TODO` in the step function with the following:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-const variantMetal = variant.options.find(
- (option) => option.option?.title === "Metal"
-)?.value
-const metalSymbol = await metalPricesModuleService
- .getMetalSymbol(variantMetal || "")
-
-if (!metalSymbol) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Variant doesn't have metal. Make sure the variant's SKU matches a metal symbol."
- )
-}
-
-if (!variant.weight) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Variant doesn't have weight. Make sure the variant has weight to calculate its price."
- )
-}
-
-// TODO retrieve custom price
-```
-
-In the code above, you first retrieve the metal option's value from the variant's options, assuming that a variant has metals if it has a `Metal` option. Then, you retrieve the metal symbol of the option's value using the `getMetalSymbol` method of the Metal Prices Module's service.
-
-If the variant doesn't have a metal in its options, the option's value is not valid, or the variant doesn't have a weight, you throw an error. The weight is necessary to calculate the price based on the metal's price per weight.
-
-Next, you'll retrieve the real-time price of the metal using the third-party service. Replace the `TODO` with the following:
-
-```ts title="src/workflows/steps/get-variant-metal-prices.ts"
-let price = variant.calculated_price?.calculated_amount || 0
-const weight = variant.weight
-const { price: metalPrice } = await metalPricesModuleService.getMetalPrice(
- metalSymbol as MetalSymbols, currencyCode
-)
-price += (metalPrice * weight * quantity)
-
-return new StepResponse(price)
-```
-
-In the code above, you first set the price to the variant's fixed price, if it has one. Then, you retrieve the metal's price using the `getMetalPrice` method of the Metal Prices Module's service.
-
-Finally, you calculate the price by multiplying the metal's price by the variant's weight and the quantity to add to the cart, then add the fixed price to it.
-
-Every step must return a `StepResponse` instance. The `StepResponse` constructor accepts the step's output as a parameter, which in this case is the variant's price.
-
-### Create addCustomToCartWorkflow
-
-Now that you have the `getVariantMetalPricesStep`, you can create the workflow that adds the item with custom pricing to the cart.
-
-Create the file `src/workflows/add-custom-to-cart.ts` with the following content:
-
-
-
-```ts title="src/workflows/add-custom-to-cart.ts" highlights={workflowHighlights}
-import { createWorkflow } from "@medusajs/framework/workflows-sdk"
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-import { QueryContext } from "@medusajs/framework/utils"
-
-type AddCustomToCartWorkflowInput = {
- cart_id: string
- item: {
- variant_id: string
- quantity: number
- metadata?: Record
- }
-}
-
-export const addCustomToCartWorkflow = createWorkflow(
- "add-custom-to-cart",
- ({ cart_id, item }: AddCustomToCartWorkflowInput) => {
- // @ts-ignore
- const { data: carts } = useQueryGraphStep({
- entity: "cart",
- filters: { id: cart_id },
- fields: ["id", "currency_code"],
- })
-
- const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "*",
- "options.*",
- "options.option.*",
- "calculated_price.*",
- ],
- filters: {
- id: item.variant_id,
- },
- options: {
- throwIfKeyNotFound: true,
- },
- context: {
- calculated_price: QueryContext({
- currency_code: carts[0].currency_code,
- }),
- },
- }).config({ name: "retrieve-variant" })
-
- // TODO add more steps
- }
-)
-```
-
-You create a workflow with `createWorkflow` from the Workflows SDK. It accepts two parameters:
-
-1. The workflow's unique name, which is `add-custom-to-cart`.
-2. A function that receives an input object with the cart's ID and the item to add to the cart. The item has the variant's ID, quantity, and optional metadata.
-
-In the function, you first retrieve the cart's details using the `useQueryGraphStep` helper step. This step uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) which is a Modules SDK tool that retrieves data across modules. You use it to retrieve the cart's ID and currency code.
-
-You also retrieve the variant's details using the `useQueryGraphStep` helper step. You pass the variant's ID to the step's filters and specify the fields to retrieve. To retrieve the variant's price based on the cart's context, you pass the cart's currency code to the `calculated_price` context.
-
-Next, you'll retrieve the variant's real-time price using the `getVariantMetalPricesStep` you created earlier. First, add the following import:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import {
- getVariantMetalPricesStep,
- GetVariantMetalPricesStepInput,
-} from "./steps/get-variant-metal-prices"
-```
-
-Then, replace the `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-const price = getVariantMetalPricesStep({
- variant: variants[0],
- currencyCode: carts[0].currency_code,
- quantity: item.quantity,
-} as unknown as GetVariantMetalPricesStepInput)
-
-// TODO add item with custom price to cart
-```
-
-You execute the `getVariantMetalPricesStep` passing it the variant's details, the cart's currency code, and the quantity of the item to add to the cart. The step returns the variant's custom price.
-
-Next, you'll add the item with the custom price to the cart. First, add the following imports at the top of the file:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import { transform } from "@medusajs/framework/workflows-sdk"
-import { addToCartWorkflow } from "@medusajs/medusa/core-flows"
-```
-
-Then, replace the `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-const itemToAdd = transform({
- item,
- price,
-}, (data) => {
- return [{
- ...data.item,
- unit_price: data.price,
- }]
-})
-
-addToCartWorkflow.runAsStep({
- input: {
- items: itemToAdd,
- cart_id,
- },
-})
-
-// TODO retrieve and return cart
-```
-
-You prepare the item to add to the cart using `transform` from the Workflows SDK. It allows you to manipulate and create variables in a workflow. After that, you use Medusa's `addToCartWorkflow` to add the item with the custom price to the cart.
-
-A workflow's constructor function has some constraints in implementation, which is why you need to use `transform` for variable manipulation. Learn more about these constraints in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
-
-Lastly, you'll retrieve the cart's details again and return them. Add the following import at the beginning of the file:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-import { WorkflowResponse } from "@medusajs/framework/workflows-sdk"
-```
-
-And replace the last `TODO` in the workflow with the following:
-
-```ts title="src/workflows/add-custom-to-cart.ts"
-// @ts-ignore
-const { data: updatedCarts } = useQueryGraphStep({
- entity: "cart",
- filters: { id: cart_id },
- fields: ["id", "items.*"],
-}).config({ name: "refetch-cart" })
-
-return new WorkflowResponse({
- cart: updatedCarts[0],
-})
-```
-
-In the code above, you retrieve the updated cart's details using the `useQueryGraphStep` helper step. To return data from the workflow, you create and return a `WorkflowResponse` instance. It accepts as a parameter the data to return, which is the updated cart.
-
-In the next step, you'll use the workflow in a custom route to add an item with a custom price to the cart.
-
-***
-
-## Step 4: Create Add Custom Item to Cart API Route
-
-Now that you've implemented the logic to add an item with a custom price to the cart, you'll expose this functionality in an API route.
-
-An API Route is an endpoint that exposes commerce features to external applications and clients, such as storefronts. You'll create an API route at the path `/store/carts/:id/line-items-metals` that executes the workflow from the previous step to add a product variant with custom price to the cart.
-
-Learn more about API routes in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md).
-
-### Create API Route
-
-An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory.
-
-The path of the API route is the file's path relative to `src/api`. So, to create the `/store/carts/:id/line-items-metals` API route, create the file `src/api/store/carts/[id]/line-items-metals/route.ts` with the following content:
-
-
-
-```ts title="src/api/store/carts/[id]/line-items-metals/route.ts"
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { HttpTypes } from "@medusajs/framework/types"
-import { addCustomToCartWorkflow } from "../../../../../workflows/add-custom-to-cart"
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const { id } = req.params
- const item = req.validatedBody
-
- const { result } = await addCustomToCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: id,
- item,
- },
- })
-
- res.status(200).json({ cart: result.cart })
-}
-```
-
-Since you export a `POST` function in this file, you're exposing a `POST` API route at `/store/carts/:id/line-items-metals`. The route handler function accepts two parameters:
-
-1. A request object with details and context on the request, such as path and body parameters.
-2. A response object to manipulate and send the response.
-
-In the function, you retrieve the cart's ID from the path parameter, and the item's details from the request body. This API route will accept the same request body parameters as Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
-
-Then, you execute the `addCustomToCartWorkflow` by invoking it, passing it the Medusa container, which is available in the request's `scope` property, then executing its `run` method. You pass the workflow's input object with the cart's ID and the item to add to the cart.
-
-Finally, you return a response with the updated cart's details.
-
-### Add Request Body Validation Middleware
-
-To ensure that the request body contains the required parameters, you'll add a middleware that validates the incoming request's body based on a defined schema.
-
-A middleware is a function executed before the API route when a request is sent to it. You define middlewares in Medusa in the `src/api/middlewares.ts` directory.
-
-Learn more about middlewares in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md).
-
-To add a validation middleware to the custom API route, create the file `src/api/middlewares.ts` with the following content:
-
-
-
-```ts title="src/api/middlewares.ts"
-import {
- defineMiddlewares,
- validateAndTransformBody,
-} from "@medusajs/framework/http"
-import {
- StoreAddCartLineItem,
-} from "@medusajs/medusa/api/store/carts/validators"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/store/carts/:id/line-items-metals",
- method: "POST",
- middlewares: [
- validateAndTransformBody(
- StoreAddCartLineItem
- ),
- ],
- },
- ],
-})
-```
-
-In this file, you export the middlewares definition using `defineMiddlewares` from the Medusa Framework. This function accepts an object having a `routes` property, which is an array of middleware configurations to apply on routes.
-
-You pass in the `routes` array an object having the following properties:
-
-- `matcher`: The route to apply the middleware on.
-- `method`: The HTTP method to apply the middleware on for the specified API route.
-- `middlewares`: An array of the middlewares to apply. You apply the `validateAndTransformBody` middleware, which validates the request body based on the `StoreAddCartLineItem` schema. This validation schema is the same schema used for Medusa's [Add Item to Cart API Route](https://docs.medusajs.com/api/store#carts_postcartsidlineitems).
-
-Any request sent to the `/store/carts/:id/line-items-metals` API route will now fail if it doesn't have the required parameters.
-
-Learn more about API route validation in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/validation/index.html.md).
-
-### Prepare to Test API Route
-
-Before you test the API route, you'll prepare and retrieve the necessary data to add a product variant with a custom price to the cart.
-
-#### Create Product with Metal Variant
-
-You'll first create a product that has a `Metal` option, and variant(s) with values for this option.
-
-Start the Medusa application with the following command:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, open the Medusa Admin dashboard at `localhost:9000/app` and log in with the email and password you created when you installed the Medusa application in the first step.
-
-Once you log in, click on Products in the sidebar, then click the Create button at the top right.
-
-
-
-Then, in the Create Product form:
-
-1. Enter a name for the product, and optionally enter other details like description.
-2. Enable the "Yes, this is a product with variants" toggle.
-3. Under Product Options, enter "Metal" for the title, and enter "Gold" for the values.
-
-Once you're done, click the Continue button.
-
-
-
-You can skip the next two steps by clicking the Continue button again, then the Publish button.
-
-Once you're done, the product's page will open. You'll now add weight to the product's Gold variant. To do that:
-
-- Scroll to the Variants section and find the Gold variant.
-- Click on the three-dots icon at its right.
-- Choose "Edit" from the dropdown.
-
-
-
-In the side window that opens, find the Weight field, enter the weight, and click the Save button.
-
-
-
-Finally, you need to set fixed prices for the variant, even if they're just `0`. To do that:
-
-1. Click on the three-dots icon at the top right of the Variants section.
-2. Choose "Edit Prices" from the dropdown.
-
-
-
-For each cell in the table, either enter a fixed price for the specified currency or leave it as `0`. Once you're done, click the Save button.
-
-
-
-You'll use this variant to add it to the cart later. You can find its ID by clicking on the variant, opening its details page. Then, on the details page, click on the icon at the right of the JSON section, and copy the ID from the JSON data.
-
-
-
-#### Retrieve Publishable API Key
-
-All requests sent to API routes starting with `/store` must have a publishable API key in the header. This ensures the request's operations are scoped to the publishable API key's associated sales channels. For example, products that aren't available in a cart's sales channel can't be added to it.
-
-To retrieve the publishable API key, on the Medusa Admin:
-
-1. Click on Settings in the sidebar at the bottom left.
-2. Click on Publishable API Keys from the sidebar, then click on a publishable API key in the list.
-
-
-
-3. Click on the publishable API key to copy it.
-
-
-
-You'll use this key when you test the API route.
-
-### Test API Route
-
-To test out the API route, you need to create a cart. A cart must be associated with a region. So, to retrieve the ID of a region in your store, send a `GET` request to the `/store/regions` API route:
-
-```bash
-curl 'localhost:9000/store/regions' \
--H 'x-publishable-api-key: {api_key}'
-```
-
-Make sure to replace `{api_key}` with the publishable API key you copied earlier.
-
-This will return a list of regions. Copy the ID of one of the regions.
-
-Then, send a `POST` request to the `/store/carts` API route to create a cart:
-
-```bash
-curl -X POST 'localhost:9000/store/carts' \
--H 'x-publishable-api-key: {api_key}' \
--H 'Content-Type: application/json' \
---data '{
- "region_id": "{region_id}"
-}'
-```
-
-Make sure to replace `{api_key}` with the publishable API key you copied earlier, and `{region_id}` with the ID of a region from the previous request.
-
-This will return the created cart. Copy the ID of the cart to use it next.
-
-Finally, to add the Gold variant to the cart with a custom price, send a `POST` request to the `/store/carts/:id/line-items-metals` API route:
-
-```bash
-curl -X POST 'localhost:9000/store/carts/{cart_id}/line-items-metals' \
--H 'x-publishable-api-key: {api_key}' \
--H 'Content-Type: application/json' \
---data '{
- "variant_id": "{variant_id}",
- "quantity": 1
-}'
-```
-
-Make sure to replace:
-
-- `{api_key}` with the publishable API key you copied earlier.
-- `{cart_id}` with the ID of the cart you created.
-- `{variant_id}` with the ID of the Gold variant you created.
-
-This will return the cart's details, where you can see in its `items` array the item with the custom price:
-
-```json title="Example Response"
-{
- "cart": {
- "items": [
- {
- "variant_id": "{variant_id}",
- "quantity": 1,
- "is_custom_price": true,
- // example custom price
- "unit_price": 2000
- }
- ]
- }
-}
-```
-
-The price will be the result of the calculation you've implemented earlier, which is the fixed price of the variant plus the real-time price of the metal, multiplied by the weight of the variant and the quantity added to the cart.
-
-This price will be reflected in the cart's total price, and you can proceed to checkout with the custom-priced item.
-
-***
-
-## Next Steps
-
-You've now implemented custom item pricing in Medusa. You can also customize the [storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md) to use the new API route to add custom-priced items to the cart.
-
-If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
-
-To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-
-
# How-to & Tutorials
In this section of the documentation, you'll find how-to guides and tutorials that will help you customize the Medusa server and admin. These guides are useful after you've learned Medusa's main concepts in the [Get Started](https://docs.medusajs.com/docs/learn/index.html.md) section of the documentation.
@@ -41660,6 +41677,1805 @@ Integrate a search engine to index and search products or other types of data in
- [Algolia](https://docs.medusajs.com/integrations/guides/algolia/index.html.md)
+# Integrate Medusa with Resend (Email Notifications)
+
+In this guide, you'll learn how to integrate Medusa with Resend.
+
+When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. Medusa's architecture supports integrating third-party services, such as an email service, that allow you to build your unique requirements around core commerce flows.
+
+[Resend](https://resend.com/docs/introduction) is an email service with an intuitive developer experience to send emails from any application type, including Node.js servers. By integrating Resend with Medusa, you can build flows to send an email when a commerce operation is performed, such as when an order is placed.
+
+This guide will teach you how to:
+
+- Install and set up Medusa.
+- Integrate Resend into Medusa for sending emails.
+- Build a flow to send an email with Resend when a customer places an order.
+
+You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
+
+[Example Repository](https://github.com/medusajs/examples/tree/main/resend-integration): Find the full code of the guide in this repository.
+
+***
+
+## Step 1: Install a Medusa Application
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+- [PostgreSQL](https://www.postgresql.org/download/)
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+You'll first be asked for the project's name. Then, when you're asked whether you want to install the Next.js storefront, choose `Y` for yes.
+
+Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name, and the Next.js storefront in a directory with the `{project-name}-storefront` name.
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credential and submit the form. Afterwards, you can login with the new user and explore the dashboard.
+
+The Next.js storefront is also running at `http://localhost:8000`.
+
+Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
+
+***
+
+## Step 2: Prepare Resend Account
+
+If you don't have a Resend Account, create one on [their website](https://resend.com/emails).
+
+In addition, Resend allows you to send emails from the address `onboarding@resend.dev` only to your account's email, which is useful for development purposes. If you have a custom domain to send emails from, add it to your Resend account's domains:
+
+1. Go to Domains from the sidebar.
+2. Click on Add Domain.
+
+
+
+3\. In the form that opens, enter your domain name and select a region close to your users, then click Add.
+
+
+
+4\. In the domain's details page that opens, you'll find DNS records to add to your DNS provider. After you add them, click on Verify DNS Records. You can start sending emails from your custom domain once it's verified.
+
+The steps to add DNS records are different for each provider, so refer to your provider's documentation or knowledge base for more details.
+
+
+
+You also need an API key to connect to your Resend account from Medusa, but you'll create that one in a later section.
+
+***
+
+## Step 3: Install Resend Dependencies
+
+In this step, you'll install two packages useful for your Resend integration:
+
+1. `resend`, which is the Resend SDK:
+
+```bash npm2yarn
+npm install resend
+```
+
+2\. [react-email](https://github.com/resend/react-email), which is a package created by Resend to create email templates with React:
+
+```bash npm2yarn
+npm install @react-email/components -E
+```
+
+You'll use these packages in the next steps.
+
+***
+
+## Step 4: Create Resend Module Provider
+
+To integrate third-party services into Medusa, you create a custom module. A module is a re-usable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
+
+Medusa's Notification Module delegates sending notifications to other modules, called module providers. In this step, you'll create a Resend Module Provider that implements sending notifications through the email channel. In later steps, you'll send email notifications with Resend when an order is placed through this provider.
+
+Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
+
+### Create Module Directory
+
+A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/resend`.
+
+### Create Service
+
+You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
+
+In this section, you'll create the Resend Module Provider's service and the methods necessary to send an email with Resend.
+
+Start by creating the file `src/modules/resend/service.ts` with the following content:
+
+```ts title="src/modules/resend/service.ts" highlights={serviceHighlights1}
+import {
+ AbstractNotificationProviderService,
+} from "@medusajs/framework/utils"
+import {
+ Logger,
+} from "@medusajs/framework/types"
+import {
+ Resend,
+} from "resend"
+
+type ResendOptions = {
+ api_key: string
+ from: string
+ html_templates?: Record
+}
+
+class ResendNotificationProviderService extends AbstractNotificationProviderService {
+ static identifier = "notification-resend"
+ private resendClient: Resend
+ private options: ResendOptions
+ private logger: Logger
+
+ // ...
+}
+
+export default ResendNotificationProviderService
+```
+
+A Notification Module Provider's service must extend the `AbstractNotificationProviderService`. It has a `send` method that you'll implement soon. The service must also have an `identifier` static property, which is a unique identifier that the Medusa application will use to register the provider in the database.
+
+The `ResendNotificationProviderService` class also has the following properties:
+
+- `resendClient` of type `Resend` (from the Resend SDK you installed in the previous step) to send emails through Resend.
+- `options` of type `ResendOptions`. Modules accept options through Medusa's configurations. This ensures that the module is reusable across applications and you don't use sensitive variables like API keys directly in your code. The options that the Resend Module Provider accepts are:
+ - `api_key`: The Resend API key.
+ - `from`: The email address to send the emails from.
+ - `html_templates`: An optional object to replace the default subject and template that the Resend Module uses. This is also useful to support custom emails in different Medusa application setups.
+- `logger` property, which is an instance of Medusa's [Logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), to log messages.
+
+To send requests using the `resendClient`, you need to initialize it in the class's constructor. So, add the following constructor to `ResendNotificationProviderService`:
+
+```ts title="src/modules/resend/service.ts"
+// ...
+
+type InjectedDependencies = {
+ logger: Logger
+}
+
+class ResendNotificationProviderService extends AbstractNotificationProviderService {
+ // ...
+ constructor(
+ { logger }: InjectedDependencies,
+ options: ResendOptions
+ ) {
+ super()
+ this.resendClient = new Resend(options.api_key)
+ this.options = options
+ this.logger = logger
+ }
+}
+```
+
+A module's service accepts two parameters:
+
+1. Dependencies resolved from the [Module's container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md), which is the module's local registry that the Medusa application adds framework tools to. In this service, you resolve the [Logger utility](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md) from the module's container.
+2. The module's options that are passed to the module in Medusa's configuration as you'll see in a later section.
+
+Using the API key passed in the module's options, you initialize the Resend client. You also set the `options` and `logger` properties.
+
+#### Validate Options Method
+
+A Notification Module Provider's service can implement a static `validateOptions` method that ensures the options passed to the module through Medusa's configurations are valid.
+
+So, add to the `ResendNotificationProviderService` the `validateOptions` method:
+
+```ts title="src/modules/resend/service.ts"
+// other imports...
+import {
+ // other imports...
+ MedusaError,
+} from "@medusajs/framework/utils"
+
+// ...
+
+class ResendNotificationProviderService extends AbstractNotificationProviderService {
+ // ...
+ static validateOptions(options: Record) {
+ if (!options.api_key) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Option `api_key` is required in the provider's options."
+ )
+ }
+ if (!options.from) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Option `from` is required in the provider's options."
+ )
+ }
+ }
+}
+```
+
+In the `validateOptions` method, you throw an error if the `api_key` or `from` options aren't passed to the module. To throw errors, you use `MedusaError` from the Modules SDK. This ensures errors follow Medusa's conventions and are displayed similar to Medusa's errors.
+
+#### Implement Template Methods
+
+Each email type has a different template and content. For example, order confirmation emails show the order's details, whereas customer confirmation emails show a greeting message to the customer.
+
+So, add two methods to the `ResendNotificationProviderService` class that retrieve the email template and subject of a specified template type:
+
+```ts title="src/modules/resend/service.ts" highlights={serviceHighlights2}
+// imports and types...
+
+enum Templates {
+ ORDER_PLACED = "order-placed",
+}
+
+const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
+ // TODO add templates
+}
+
+class ResendNotificationProviderService extends AbstractNotificationProviderService {
+ // ...
+ getTemplate(template: Templates) {
+ if (this.options.html_templates?.[template]) {
+ return this.options.html_templates[template].content
+ }
+ const allowedTemplates = Object.keys(templates)
+
+ if (!allowedTemplates.includes(template)) {
+ return null
+ }
+
+ return templates[template]
+ }
+
+ getTemplateSubject(template: Templates) {
+ if (this.options.html_templates?.[template]?.subject) {
+ return this.options.html_templates[template].subject
+ }
+ switch(template) {
+ case Templates.ORDER_PLACED:
+ return "Order Confirmation"
+ default:
+ return "New Email"
+ }
+ }
+}
+```
+
+You first define a `Templates` enum, which holds the names of supported template types. You can add more template types to this enum later. You also define a `templates` variable that specifies the React template for each template type. You'll add templates to this variable later.
+
+In the `ResendNotificationProviderService` you add two methods:
+
+- `getTemplate`: Retrieve the template of a template type. If the `html_templates` option is set for the specified template type, you return its `content`'s value. Otherwise, you retrieve the template from the `templates` variable.
+- `getTemplateSubject`: Retrieve the subject of a template type. If a `subject` is passed for the template type in the `html_templates`, you return its value. Otherwise, you return a subject based on the template type.
+
+You'll use these methods in the `send` method next.
+
+#### Implement Send Method
+
+In this section, you'll implement the `send` method of `ResendNotificationProviderService`. When you send a notification through the email channel later using the Notification Module, the Notification Module's service will use this `send` method under the hood to send the email with Resend.
+
+In the `send` method, you'll retrieve the template and subject of the email template, then send the email using the Resend client.
+
+Add the `send` method to the `ResendNotificationProviderService` class:
+
+```ts title="src/modules/resend/service.ts" highlights={serviceHighlights3}
+// other imports...
+import {
+ // ...
+ ProviderSendNotificationDTO,
+ ProviderSendNotificationResultsDTO,
+} from "@medusajs/framework/types"
+import {
+ // ...
+ CreateEmailOptions,
+} from "resend"
+
+class ResendNotificationProviderService extends AbstractNotificationProviderService {
+ // ...
+ async send(
+ notification: ProviderSendNotificationDTO
+ ): Promise {
+ const template = this.getTemplate(notification.template as Templates)
+
+ if (!template) {
+ this.logger.error(`Couldn't find an email template for ${notification.template}. The valid options are ${Object.values(Templates)}`)
+ return {}
+ }
+
+ const emailOptions: CreateEmailOptions = {
+ from: this.options.from,
+ to: [notification.to],
+ subject: this.getTemplateSubject(notification.template as Templates),
+ html: "",
+ }
+
+ if (typeof template === "string") {
+ emailOptions.html = template
+ } else {
+ emailOptions.react = template(notification.data)
+ delete emailOptions.html
+ }
+
+ const { data, error } = await this.resendClient.emails.send(emailOptions)
+
+ if (error) {
+ this.logger.error(`Failed to send email`, error)
+ return {}
+ }
+
+ return { id: data.id }
+ }
+}
+```
+
+The `send` method receives the notification details object as a parameter. Some of its properties include:
+
+- `to`: The address to send the notification to.
+- `template`: The template type of the notification.
+- `data`: The data useful for the email type. For example, when sending an order-confirmation email, `data` would hold the order's details.
+
+In the method, you retrieve the template and subject of the email using the methods you defined earlier. Then, you put together the data to pass to Resend, such as the email address to send the notification to and the email address to send from. Also, if the email's template is a string, it's passed as an HTML template. Otherwise, it's passed as a React template.
+
+Finally, you use the `emails.send` method of the Resend client to send the email. If an error occurs you log it in the terminal. Otherwise, you return the ID of the send email as received from Resend. Medusa uses this ID when creating the notification in its database.
+
+### Export Module Definition
+
+The `ResendNotificationProviderService` class now has the methods necessary to start sending emails.
+
+Next, you must export the module provider's definition, which lets Medusa know what module this provider belongs to and its service.
+
+Create the file `src/modules/resend/index.ts` with the following content:
+
+```ts title="src/modules/resend/index.ts"
+import {
+ ModuleProvider,
+ Modules,
+} from "@medusajs/framework/utils"
+import ResendNotificationProviderService from "./service"
+
+export default ModuleProvider(Modules.NOTIFICATION, {
+ services: [ResendNotificationProviderService],
+})
+```
+
+You export the module provider's definition using `ModuleProvider` from the Modules SDK. It accepts as a first parameter the name of the module that this provider belongs to, which is the Notification Module. It also accepts as a second parameter an object having a `service` property indicating the provider's service.
+
+### Add Module to Configurations
+
+Finally, to register modules and module providers in Medusa, you must add them to Medusa's configurations.
+
+Medusa's configurations are set in the `medusa-config.ts` file, which is at the root directory of your Medusa application. The configuration object accepts a `modules` array, whose value is an array of modules to add to the application.
+
+Add the `modules` property to the exported configurations in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/notification",
+ options: {
+ providers: [
+ {
+ resolve: "./src/modules/resend",
+ id: "resend",
+ options: {
+ channels: ["email"],
+ api_key: process.env.RESEND_API_KEY,
+ from: process.env.RESEND_FROM_EMAIL,
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+In the `modules` array, you pass a module object having the following properties:
+
+- `resolve`: The NPM package of the Notification Module. Since the Resend Module is a Notification Module Provider, it'll be passed in the options of the Notification Module.
+- `options`: An object of options to pass to the module. It has a `providers` property which is an array of module providers to register. Each module provider object has the following properties:
+ - `resolve`: The path to the module provider to register in the application. It can also be the name of an NPM package.
+ - `id`: A unique ID, which Medusa will use along with the `identifier` static property that you set earlier in the class to identify this module provider.
+ - `options`: An object of options to pass to the module provider. These are the options you expect and use in the module provider's service. You must also specify the `channels` option, which indicates the channels that this provider sends notifications through.
+
+Some of the module's options, such as the Resend API key, are set in environment variables. So, add the following environment variables to `.env`:
+
+```shell
+RESEND_FROM_EMAIL=onboarding@resend.dev
+RESEND_API_KEY=
+```
+
+Where:
+
+- `RESEND_FROM_EMAIL`: The email to send emails from. If you've configured the custom domain as explained in [Step 2](#step-2-prepare-resend-account), change this email to an email from your custom domain. Otherwise, you can use `onboarding@resend.dev` for development purposes.
+- `RESEND_API_KEY` is the API key of your Resend account. To retrieve it:
+ - Go to API Keys in the sidebar.
+ - Click on the Create API Key button.
+
+
+
+- In the form that opens, enter a name for the API key (for example, Medusa). You can keep its permissions to Full Access or change it to Sending Access. Once you're done, click Add.
+
+
+
+- A new pop-up will show with your API key hidden. Copy it before closing the pop-up, since you can't access the key again afterwards. Use its value for the `RESEND_API_KEY` environment variable.
+
+
+
+Your Resend Module Provider is all set up. You'll test it out in a later section.
+
+***
+
+## Step 5: Add Order Confirmation Template
+
+In this step, you'll add a React template for order confirmation emails. You'll create it using the [react-email](https://github.com/resend/react-email) package you installed earlier. You can follow the same steps for other email templates, such as for customer confirmation.
+
+Create the directory `src/modules/resend/emails` that will hold the email templates. Then, to add the template for order confirmation, create the file `src/modules/resend/emails/order-placed.tsx` with the following content:
+
+```tsx title="src/modules/resend/emails/order-placed.tsx" highlights={templateHighlights}
+import { Text, Column, Container, Heading, Html, Img, Row, Section } from "@react-email/components"
+import { BigNumberValue, OrderDTO } from "@medusajs/framework/types"
+
+type OrderPlacedEmailProps = {
+ order: OrderDTO
+}
+
+function OrderPlacedEmailComponent({ order }: OrderPlacedEmailProps) {
+ const formatter = new Intl.NumberFormat([], {
+ style: "currency",
+ currencyDisplay: "narrowSymbol",
+ currency: order.currency_code,
+ })
+
+ const formatPrice = (price: BigNumberValue) => {
+ if (typeof price === "number") {
+ return formatter.format(price)
+ }
+
+ if (typeof price === "string") {
+ return formatter.format(parseFloat(price))
+ }
+
+ return price?.toString() || ""
+ }
+
+ return (
+
+ Thank you for your order
+ {order.email}'s Items
+
+ {order.items.map((item) => {
+ return (
+
+
+
+
+
+
+
+ {item.product_title}
+
+ {item.variant_title}
+ {formatPrice(item.total)}
+
+
+
+ )
+ })}
+
+
+ )
+}
+
+export const orderPlacedEmail = (props: OrderPlacedEmailProps) => (
+
+)
+```
+
+You define the `OrderPlacedEmailComponent` which is a React email template that shows the order's details, such as items and their totals. The component accepts an `order` object as a prop.
+
+You also export an `orderPlacedEmail` function, which accepts props as an input and returns the `OrderPlacedEmailComponent` passing it the props. Because you can't use JSX syntax in `src/modules/resend/service.ts`, you'll import this function instead.
+
+Next, update the `templates` variable in `src/modules/resend/service.ts` to assign this template to the `order-placed` template type:
+
+```ts title="src/modules/resend/service.ts"
+// other imports...
+import { orderPlacedEmail } from "./emails/order-placed"
+
+const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
+ [Templates.ORDER_PLACED]: orderPlacedEmail,
+}
+```
+
+The `ResendNotificationProviderService` will now use the `OrderPlacedEmailComponent` as the template of order confirmation emails.
+
+***
+
+## Step 6: Send Email when Order is Placed
+
+Medusa has an event system that emits an event when a commerce operation is performed. You can then listen and handle that event in an asynchronous function called a subscriber.
+
+So, to send a confirmation email when a customer places an order, which is a commerce operation that Medusa already implements, you don't need to extend or hack your way into Medusa's implementation as you would do with other commerce platforms.
+
+Instead, you'll create a subscriber that listens to the `order.placed` event and sends an email when the event is emitted.
+
+Learn more about Medusa's event system in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md).
+
+### Send Order Confirmation Email Workflow
+
+To send the order confirmation email, you need to retrieve the order's details first, then use the Notification Module's service to send the email. To implement this flow, you'll create a workflow.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in a subscriber.
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
+
+#### Send Notification Step
+
+You'll start by implementing the step of the workflow that sends the notification. To do that, create the file `src/workflows/steps/send-notification.ts` with the following content:
+
+```ts title="src/workflows/steps/send-notification.ts"
+import { Modules } from "@medusajs/framework/utils"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { CreateNotificationDTO } from "@medusajs/framework/types"
+
+export const sendNotificationStep = createStep(
+ "send-notification",
+ async (data: CreateNotificationDTO[], { container }) => {
+ const notificationModuleService = container.resolve(
+ Modules.NOTIFICATION
+ )
+ const notification = await notificationModuleService.createNotifications(data)
+ return new StepResponse(notification)
+ }
+)
+```
+
+You define the `sendNotificationStep` using the `createStep` function that accepts two parameters:
+
+- A string indicating the step's unique name.
+- The step's function definition as a second parameter. It accepts the step's input as a first parameter, and an object of options as a second.
+
+The `container` property in the second parameter is an instance of the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools, such a module's service, that you can resolve to utilize their functionalities.
+
+The Medusa container is accessible by all customizations, such as workflows and subscribers, except for modules. Each module has its own container with framework tools like the Logger utility.
+
+In the step function, you resolve the Notification Module's service, and use its `createNotifications` method, passing it the notification's data that the step receives as an input.
+
+The step returns an instance of `StepResponse`, which must be returned by any step. It accepts as a parameter the data to return to the workflow that executed this step.
+
+#### Workflow Implementation
+
+You'll now create the workflow that uses the `sendNotificationStep` to send the order confirmation email.
+
+Create the file `src/workflows/send-order-confirmation.ts` with the following content:
+
+```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+import { sendNotificationStep } from "./steps/send-notification"
+
+type WorkflowInput = {
+ id: string
+}
+
+export const sendOrderConfirmationWorkflow = createWorkflow(
+ "send-order-confirmation",
+ ({ id }: WorkflowInput) => {
+ // @ts-ignore
+ const { data: orders } = useQueryGraphStep({
+ entity: "order",
+ fields: [
+ "id",
+ "email",
+ "currency_code",
+ "total",
+ "items.*",
+ ],
+ filters: {
+ id,
+ },
+ })
+
+ const notification = sendNotificationStep([{
+ to: orders[0].email,
+ channel: "email",
+ template: "order-placed",
+ data: {
+ order: orders[0],
+ },
+ }])
+
+ return new WorkflowResponse(notification)
+ }
+)
+```
+
+You create a workflow using `createWorkflow` from the Workflows SDK. It accepts the workflow's unique name as a first parameter.
+
+It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps:
+
+1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details.
+2. `sendNotificationStep` which is the step you implemented. You pass it an array with one object, which is the notification's details having following properties:
+ - `to`: The address to send the email to. You pass the customer's email that is stored in the order.
+ - `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service.
+ - `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here.
+ - `data`: The data to pass to the email template, which is the order's details.
+
+A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
+
+You'll execute the workflow when you create the subscriber next.
+
+#### Add the Order Placed Subscriber
+
+Now that you have the workflow to send an order-confirmation email, you'll execute it in a subscriber that's executed whenever an order is placed.
+
+You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. So, create the file `src/subscribers/order-placed.ts` with the following content:
+
+```ts title="src/subscribers/order-placed.ts" highlights={subscriberHighlights}
+import type {
+ SubscriberArgs,
+ SubscriberConfig,
+} from "@medusajs/framework"
+import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
+
+export default async function orderPlacedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ await sendOrderConfirmationWorkflow(container)
+ .run({
+ input: {
+ id: data.id,
+ },
+ })
+}
+
+export const config: SubscriberConfig = {
+ event: "order.placed",
+}
+```
+
+A subscriber file exports:
+
+- An asynchronous function that's executed whenever the associated event is emitted, which is the `order.placed` event.
+- A configuration object with an `event` property indicating the event the subscriber is listening to.
+
+The subscriber function accepts the event's details as a first paramter which has a `data` property that holds the data payload of the event. For example, Medusa emits the `order.placed` event with the order's ID in the data payload. The function also accepts as a second parameter the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
+
+In the function, you execute the `sendOrderConfirmationWorkflow` by invoking it, passing it the `container`, then using its `run` method. The `run` method accepts an object having an `input` property, which is the input to pass to the workflow. You pass the ID of the placed order as received in the event's data payload.
+
+This subscriber now runs whenever an order is placed. You'll see this in action in the next section.
+
+***
+
+## Test it Out: Place an Order
+
+To test out the Resend integration, you'll place an order using the [Next.js storefront](https://docs.medusajs.com/docs/learn/storefront-development/nextjs-starter/index.html.md) that you installed as part of installing Medusa.
+
+Start your Medusa application first:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, in the Next.js storefront's directory (which was installed in a directory outside of the Medusa application's directory with the name `{project-name}-storefront`, where `{project-name}` is the name of the Medusa application's directory), run the following command to start the storefront:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, open the storefront in your browser at `http://localhost:8000` and:
+
+1. Go to Menu -> Store.
+
+
+
+2\. Click on a product, select its options, and add it to the cart.
+
+
+
+3\. Click on Cart at the top right, then click Go to Cart.
+
+
+
+4\. On the cart's page, click on the "Go to checkout" button.
+
+
+
+5\. On the checkout page, when entering the shipping address, make sure to set the email to your Resend account's email if you didn't set up a custom domain.
+
+
+
+6\. After entering the shipping address, choose a delivery and payment methods, then click the Place Order button.
+
+Once the order is placed, you'll find the following message logged in the Medusa application's terminal:
+
+```bash
+info: Processing order.placed which has 1 subscribers
+```
+
+This indicates that the `order.placed` event was emitted and its subscriber, which you added in the previous step, is executed.
+
+If you check the inbox of the email address you specified in the shipping address, you'll find a new email with the order's details.
+
+
+
+***
+
+## Next Steps
+
+You've now integrated Medusa with Resend. You can add more templates for other emails, such as customer registration confirmation, user invites, and more. Check out the [Events Reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/events-reference/index.html.md) for a list of all events that the Medusa application emits.
+
+If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+
+
+# How to Build Magento Data Migration Plugin
+
+In this tutorial, you'll learn how to build a [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) that migrates data, such as products, from Magento to Medusa.
+
+Magento is known for its customization capabilities. However, its monolithic architecture imposes limitations on business requirements, often forcing development teams to implement hacky workarounds. Over time, these customizations become challenging to maintain, especially as the business scales, leading to increased technical debt and slower feature delivery.
+
+Medusa's modular architecture allows you to build a custom digital commerce platform that meets your business requirements without the limitations of a monolithic system. By migrating from Magento to Medusa, you can take advantage of Medusa's modern technology stack to build a scalable and flexible commerce platform that grows with your business.
+
+By following this tutorial, you'll create a Medusa plugin that migrates data from a Magento server to a Medusa application in minimal time. You can re-use this plugin across multiple Medusa applications, allowing you to adopt Medusa across your projects.
+
+## Summary
+
+### Prerequisites
+
+
+
+This tutorial will teach you how to:
+
+- Install and set up a Medusa application project.
+- Install and set up a Medusa plugin.
+- Implement a Magento Module in the plugin to connect to Magento's APIs and retrieve products.
+ - This guide will only focus on migrating product data from Magento to Medusa. You can extend the implementation to migrate other data, such as customers, orders, and more.
+- Trigger data migration from Magento to Medusa in a scheduled job.
+
+You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer.
+
+
+
+[Example Repository](https://github.com/medusajs/examples/tree/main/migrate-from-magento): Find the full code of the guide in this repository. The repository also includes additional features, such as triggering migrations from the Medusa Admin dashboard.
+
+***
+
+## Step 1: Install a Medusa Application
+
+You'll first install a Medusa application that exposes core commerce features through REST APIs. You'll later install the Magento plugin in this application to test it out.
+
+### Prerequisites
+
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
+- [PostgreSQL](https://www.postgresql.org/download/)
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+You'll be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
+
+Afterward, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Refer to the [Medusa Architecture](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md) documentation to learn more.
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterward, you can log in with the new user and explore the dashboard.
+
+Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
+
+***
+
+## Step 2: Install a Medusa Plugin Project
+
+A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. You can add in the plugin [API Routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), and other customizations, as you'll see in this guide. Afterward, you can test it out locally in a Medusa application, then publish it to npm to install and use it in any Medusa application.
+
+Refer to the [Plugins](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) documentation to learn more about plugins.
+
+A Medusa plugin is set up in a different project, giving you the flexibility in building and publishing it, while providing you with the tools to test it out locally in a Medusa application.
+
+To create a new Medusa plugin project, run the following command in a directory different than that of the Medusa application:
+
+```bash npm2yarn
+npx create-medusa-app@latest medusa-plugin-magento --plugin
+```
+
+Where `medusa-plugin-magento` is the name of the plugin's directory and the name set in the plugin's `package.json`. So, if you wish to publish it to NPM later under a different name, you can change it here in the command or later in `package.json`.
+
+Once the installation process is done, a new directory named `medusa-plugin-magento` will be created with the plugin project files.
+
+
+
+***
+
+## Step 3: Set up Plugin in Medusa Application
+
+Before you start your development, you'll set up the plugin in the Medusa application you installed in the first step. This will allow you to test the plugin during your development process.
+
+In the plugin's directory, run the following command to publish the plugin to the local package registry:
+
+```bash title="Plugin project"
+npx medusa plugin:publish
+```
+
+This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
+
+Next, you'll install the plugin in the Medusa application from the local registry.
+
+If you've installed your Medusa project before v2.3.1, you must install [yalc](https://github.com/wclr/yalc) as a development dependency first.
+
+Run the following command in the Medusa application's directory to install the plugin:
+
+```bash title="Medusa application"
+npx medusa plugin:add medusa-plugin-magento
+```
+
+This command installs the plugin in the Medusa application from the local package registry.
+
+Next, register the plugin in the `medusa-config.ts` file of the Medusa application:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ plugins: [
+ {
+ resolve: "medusa-plugin-magento",
+ options: {
+ // TODO add options
+ },
+ },
+ ],
+})
+```
+
+You add the plugin to the array of plugins. Later, you'll pass options useful to retrieve data from Magento.
+
+Finally, to ensure your plugin's changes are constantly published to the local registry, simplifying your testing process, keep the following command running in the plugin project during development:
+
+```bash title="Plugin project"
+npx medusa plugin:develop
+```
+
+***
+
+## Step 4: Implement Magento Module
+
+To connect to external applications in Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
+
+In this step, you'll create a Magento Module in the Magento plugin that connects to a Magento server's REST APIs and retrieves data, such as products.
+
+Refer to the [Modules](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) documentation to learn more about modules.
+
+### Create Module Directory
+
+A module is created under the `src/modules` directory of your plugin. So, create the directory `src/modules/magento`.
+
+
+
+### Create Module's Service
+
+You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to external systems or the database, which is useful if your module defines tables in the database.
+
+In this section, you'll create the Magento Module's service that connects to Magento's REST APIs and retrieves data.
+
+Start by creating the file `src/modules/magento/service.ts` in the plugin with the following content:
+
+
+
+```ts title="src/modules/magento/service.ts"
+type Options = {
+ baseUrl: string
+ storeCode?: string
+ username: string
+ password: string
+ migrationOptions?: {
+ imageBaseUrl?: string
+ }
+}
+
+export default class MagentoModuleService {
+ private options: Options
+
+ constructor({}, options: Options) {
+ this.options = {
+ ...options,
+ storeCode: options.storeCode || "default",
+ }
+ }
+}
+```
+
+You create a `MagentoModuleService` that has an `options` property to store the module's options. These options include:
+
+- `baseUrl`: The base URL of the Magento server.
+- `storeCode`: The store code of the Magento store, which is `default` by default.
+- `username`: The username of a Magento admin user to authenticate with the Magento server.
+- `password`: The password of the Magento admin user.
+- `migrationOptions`: Additional options useful for migrating data, such as the base URL to use for product images.
+
+The service's constructor accepts as a first parameter the [Module Container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md), which allows you to access resources available for the module. As a second parameter, it accepts the module's options.
+
+### Add Authentication Logic
+
+To authenticate with the Magento server, you'll add a method to the service that retrieves an access token from Magento using the username and password in the options. This access token is used in subsequent requests to the Magento server.
+
+First, add the following property to the `MagentoModuleService` class:
+
+```ts title="src/modules/magento/service.ts"
+export default class MagentoModuleService {
+ private accessToken: {
+ token: string
+ expiresAt: Date
+ }
+ // ...
+}
+```
+
+You add an `accessToken` property to store the access token and its expiration date. The access token Magento returns expires after four hours, so you store the expiration date to know when to refresh the token.
+
+Next, add the following `authenticate` method to the `MagentoModuleService` class:
+
+```ts title="src/modules/magento/service.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+
+export default class MagentoModuleService {
+ // ...
+ async authenticate() {
+ const response = await fetch(`${this.options.baseUrl}/rest/${this.options.storeCode}/V1/integration/admin/token`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ username: this.options.username, password: this.options.password }),
+ })
+
+ const token = await response.text()
+
+ if (!response.ok) {
+ throw new MedusaError(MedusaError.Types.UNAUTHORIZED, `Failed to authenticate with Magento: ${token}`)
+ }
+
+ this.accessToken = {
+ token: token.replaceAll("\"", ""),
+ expiresAt: new Date(Date.now() + 4 * 60 * 60 * 1000), // 4 hours in milliseconds
+ }
+ }
+}
+```
+
+You create an `authenticate` method that sends a POST request to the Magento server's `/rest/{storeCode}/V1/integration/admin/token` endpoint, passing the username and password in the request body.
+
+If the request is successful, you store the access token and its expiration date in the `accessToken` property. If the request fails, you throw a `MedusaError` with the error message returned by Magento.
+
+Lastly, add an `isAccessTokenExpired` method that checks if the access token has expired:
+
+```ts title="src/modules/magento/service.ts"
+export default class MagentoModuleService {
+ // ...
+ async isAccessTokenExpired(): Promise {
+ return !this.accessToken || this.accessToken.expiresAt < new Date()
+ }
+}
+```
+
+In the `isAccessTokenExpired` method, you return a boolean indicating whether the access token has expired. You'll use this in later methods to check if you need to refresh the access token.
+
+### Retrieve Products from Magento
+
+Next, you'll add a method that retrieves products from Magento. Due to limitations in Magento's API that makes it difficult to differentiate between simple products that don't belong to a configurable product and those that do, you'll only retrieve configurable products and their children. You'll also retrieve the configurable attributes of the product, such as color and size.
+
+First, you'll add some types to represent a Magento product and its attributes. Create the file `src/modules/magento/types.ts` in the plugin with the following content:
+
+
+
+```ts title="src/modules/magento/types.ts"
+export type MagentoProduct = {
+ id: number
+ sku: string
+ name: string
+ price: number
+ status: number
+ // not handling other types
+ type_id: "simple" | "configurable"
+ created_at: string
+ updated_at: string
+ extension_attributes: {
+ category_links: {
+ category_id: string
+ }[]
+ configurable_product_links?: number[]
+ configurable_product_options?: {
+ id: number
+ attribute_id: string
+ label: string
+ position: number
+ values: {
+ value_index: number
+ }[]
+ }[]
+ }
+ media_gallery_entries: {
+ id: number
+ media_type: string
+ label: string
+ position: number
+ disabled: boolean
+ types: string[]
+ file: string
+ }[]
+ custom_attributes: {
+ attribute_code: string
+ value: string
+ }[]
+ // added by module
+ children?: MagentoProduct[]
+}
+
+export type MagentoAttribute = {
+ attribute_code: string
+ attribute_id: number
+ default_frontend_label: string
+ options: {
+ label: string
+ value: string
+ }[]
+}
+
+export type MagentoPagination = {
+ search_criteria: {
+ filter_groups: [],
+ page_size: number
+ current_page: number
+ }
+ total_count: number
+}
+
+export type MagentoPaginatedResponse = {
+ items: TData[]
+} & MagentoPagination
+```
+
+You define the following types:
+
+- `MagentoProduct`: Represents a product in Magento.
+- `MagentoAttribute`: Represents an attribute in Magento.
+- `MagentoPagination`: Represents the pagination information returned by Magento's API.
+- `MagentoPaginatedResponse`: Represents a paginated response from Magento's API for a specific item type, such as products.
+
+Next, add the `getProducts` method to the `MagentoModuleService` class:
+
+```ts title="src/modules/magento/service.ts"
+export default class MagentoModuleService {
+ // ...
+ async getProducts(options?: {
+ currentPage?: number
+ pageSize?: number
+ }): Promise<{
+ products: MagentoProduct[]
+ attributes: MagentoAttribute[]
+ pagination: MagentoPagination
+ }> {
+ const { currentPage = 1, pageSize = 100 } = options || {}
+ const getAccessToken = await this.isAccessTokenExpired()
+ if (getAccessToken) {
+ await this.authenticate()
+ }
+
+ // TODO prepare query params
+ }
+}
+```
+
+The `getProducts` method receives an optional `options` object with the `currentPage` and `pageSize` properties. So far, you check if the access token has expired and, if so, retrieve a new one using the `authenticate` method.
+
+Next, you'll prepare the query parameters to pass in the request that retrieves products. Replace the `TODO` with the following:
+
+```ts title="src/modules/magento/service.ts"
+const searchQuery = new URLSearchParams()
+// pass pagination parameters
+searchQuery.append(
+ "searchCriteria[currentPage]",
+ currentPage?.toString() || "1"
+)
+searchQuery.append(
+ "searchCriteria[pageSize]",
+ pageSize?.toString() || "100"
+)
+
+// retrieve only configurable products
+searchQuery.append(
+ "searchCriteria[filter_groups][1][filters][0][field]",
+ "type_id"
+)
+searchQuery.append(
+ "searchCriteria[filter_groups][1][filters][0][value]",
+ "configurable"
+)
+searchQuery.append(
+ "searchCriteria[filter_groups][1][filters][0][condition_type]",
+ "in"
+)
+
+// TODO send request to retrieve products
+```
+
+You create a `searchQuery` object to store the query parameters to pass in the request. Then, you add the pagination parameters and the filter to retrieve only configurable products.
+
+Next, you'll send the request to retrieve products from Magento. Replace the `TODO` with the following:
+
+```ts title="src/modules/magento/service.ts"
+const { items: products, ...pagination }: MagentoPaginatedResponse = await fetch(
+ `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/products?${searchQuery}`,
+ {
+ headers: {
+ "Authorization": `Bearer ${this.accessToken.token}`,
+ },
+ }
+).then((res) => res.json())
+.catch((err) => {
+ console.log(err)
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ `Failed to get products from Magento: ${err.message}`
+ )
+})
+
+// TODO prepare products
+```
+
+You send a `GET` request to the Magento server's `/rest/{storeCode}/V1/products` endpoint, passing the query parameters in the URL. You also pass the access token in the `Authorization` header.
+
+Next, you'll prepare the retrieved products by retrieving their children, configurable attributes, and modifying their image URLs. Replace the `TODO` with the following:
+
+```ts title="src/modules/magento/service.ts"
+const attributeIds: string[] = []
+
+await promiseAll(
+ products.map(async (product) => {
+ // retrieve its children
+ product.children = await fetch(
+ `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/configurable-products/${product.sku}/children`,
+ {
+ headers: {
+ "Authorization": `Bearer ${this.accessToken.token}`,
+ },
+ }
+ ).then((res) => res.json())
+ .catch((err) => {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ `Failed to get product children from Magento: ${err.message}`
+ )
+ })
+
+ product.media_gallery_entries = product.media_gallery_entries.map(
+ (entry) => ({
+ ...entry,
+ file: `${this.options.migrationOptions?.imageBaseUrl}${entry.file}`,
+ }
+ ))
+
+ attributeIds.push(...(
+ product.extension_attributes.configurable_product_options?.map(
+ (option) => option.attribute_id) || []
+ )
+ )
+ })
+)
+
+// TODO retrieve attributes
+```
+
+You loop over the retrieved products and retrieve their children using the `/rest/{storeCode}/V1/configurable-products/{sku}/children` endpoint. You also modify the image URLs to use the base URL in the migration options, if provided.
+
+In addition, you store the IDs of the configurable products' attributes in the `attributeIds` array. You'll add a method that retrieves these attributes.
+
+Add the new method `getAttributes` to the `MagentoModuleService` class:
+
+```ts title="src/modules/magento/service.ts"
+export default class MagentoModuleService {
+ // ...
+ async getAttributes({
+ ids,
+ }: {
+ ids: string[]
+ }): Promise {
+ const getAccessToken = await this.isAccessTokenExpired()
+ if (getAccessToken) {
+ await this.authenticate()
+ }
+
+ // filter by attribute IDs
+ const searchQuery = new URLSearchParams()
+ searchQuery.append(
+ "searchCriteria[filter_groups][0][filters][0][field]",
+ "attribute_id"
+ )
+ searchQuery.append(
+ "searchCriteria[filter_groups][0][filters][0][value]",
+ ids.join(",")
+ )
+ searchQuery.append(
+ "searchCriteria[filter_groups][0][filters][0][condition_type]",
+ "in"
+ )
+
+ const {
+ items: attributes,
+ }: MagentoPaginatedResponse = await fetch(
+ `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/products/attributes?${searchQuery}`,
+ {
+ headers: {
+ "Authorization": `Bearer ${this.accessToken.token}`,
+ },
+ }
+ ).then((res) => res.json())
+ .catch((err) => {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ `Failed to get attributes from Magento: ${err.message}`
+ )
+ })
+
+ return attributes
+ }
+}
+```
+
+The `getAttributes` method receives an object with the `ids` property, which is an array of attribute IDs. You check if the access token has expired and, if so, retrieve a new one using the `authenticate` method.
+
+Next, you prepare the query parameters to pass in the request to retrieve attributes. You send a `GET` request to the Magento server's `/rest/{storeCode}/V1/products/attributes` endpoint, passing the query parameters in the URL. You also pass the access token in the `Authorization` header.
+
+Finally, you return the retrieved attributes.
+
+Now, go back to the `getProducts` method and replace the `TODO` with the following:
+
+```ts title="src/modules/magento/service.ts"
+const attributes = await this.getAttributes({ ids: attributeIds })
+
+return { products, attributes, pagination }
+```
+
+You retrieve the configurable products' attributes using the `getAttributes` method and return the products, attributes, and pagination information.
+
+You'll use this method in a later step to retrieve products from Magento.
+
+### Export Module Definition
+
+The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
+
+So, create the file `src/modules/magento/index.ts` with the following content:
+
+
+
+```ts title="src/modules/magento/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import MagentoModuleService from "./service"
+
+export const MAGENTO_MODULE = "magento"
+
+export default Module(MAGENTO_MODULE, {
+ service: MagentoModuleService,
+})
+```
+
+You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
+
+1. The module's name, which is `magento`.
+2. An object with a required property `service` indicating the module's service.
+
+You'll later use the module's service to retrieve products from Magento.
+
+### Pass Options to Plugin
+
+As mentioned earlier when you registered the plugin in the Medusa Application's `medusa-config.ts` file, you can pass options to the plugin. These options are then passed to the modules in the plugin.
+
+So, add the following options to the plugin's registration in the `medusa-config.ts` file of the Medusa application:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ plugins: [
+ {
+ resolve: "medusa-plugin-magento",
+ options: {
+ baseUrl: process.env.MAGENTO_BASE_URL,
+ username: process.env.MAGENTO_USERNAME,
+ password: process.env.MAGENTO_PASSWORD,
+ migrationOptions: {
+ imageBaseUrl: process.env.MAGENTO_IMAGE_BASE_URL,
+ },
+ },
+ },
+ ],
+})
+```
+
+You pass the options that you defined in the `MagentoModuleService`. Make sure to also set their environment variables in the `.env` file:
+
+```bash
+MAGENTO_BASE_URL=https://magento.example.com
+MAGENTO_USERNAME=admin
+MAGENTO_PASSWORD=password
+MAGENTO_IMAGE_BASE_URL=https://magento.example.com/pub/media/catalog/product
+```
+
+Where:
+
+- `MAGENTO_BASE_URL`: The base URL of the Magento server. It can also be a local URL, such as `http://localhost:8080`.
+- `MAGENTO_USERNAME`: The username of a Magento admin user to authenticate with the Magento server.
+- `MAGENTO_PASSWORD`: The password of the Magento admin user.
+- `MAGENTO_IMAGE_BASE_URL`: The base URL to use for product images. Magento stores product images in the `pub/media/catalog/product` directory, so you can reference them directly or use a CDN URL. If the URLs of product images in the Medusa server already have a different base URL, you can omit this option.
+
+Medusa supports integrating third-party services, such as [S3](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/file/s3/index.html.md), in a File Module Provider. Refer to the [File Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/file/index.html.md) documentation to find other module providers and how to create a custom provider.
+
+You can now use the Magento Module to migrate data, which you'll do in the next steps.
+
+***
+
+## Step 5: Build Product Migration Workflow
+
+In this section, you'll add the feature to migrate products from Magento to Medusa. To implement this feature, you'll use a workflow.
+
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an API route or a scheduled job.
+
+By implementing the migration feature in a workflow, you ensure that the data remains consistent and that the migration process can be rolled back if an error occurs.
+
+Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) documentation to learn more about workflows.
+
+### Workflow Steps
+
+The workflow you'll create will have the following steps:
+
+- [getMagentoProductsStep](#getMagentoProductsStep): Retrieve products from Magento using the Magento Module.
+- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve Medusa store details, which you'll need when creating the products.
+- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve a shipping profile, which you'll associate the created products with.
+- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve Magento products that are already in Medusa to update them, instead of creating them.
+- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md): Create products in the Medusa application.
+- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md): Update existing products in the Medusa application.
+
+You only need to implement the `getMagentoProductsStep` step, which retrieves the products from Magento. The other steps and workflows are provided by Medusa's `@medusajs/medusa/core-flows` package.
+
+### getMagentoProductsStep
+
+The first step of the workflow retrieves and returns the products from Magento.
+
+In your plugin, create the file `src/workflows/steps/get-magento-products.ts` with the following content:
+
+
+
+```ts title="src/workflows/steps/get-magento-products.ts"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { MAGENTO_MODULE } from "../../modules/magento"
+import MagentoModuleService from "../../modules/magento/service"
+
+type GetMagentoProductsInput = {
+ currentPage: number
+ pageSize: number
+}
+
+export const getMagentoProductsStep = createStep(
+ "get-magento-products",
+ async ({ currentPage, pageSize }: GetMagentoProductsInput, { container }) => {
+ const magentoModuleService: MagentoModuleService =
+ container.resolve(MAGENTO_MODULE)
+
+ const response = await magentoModuleService.getProducts({
+ currentPage,
+ pageSize,
+ })
+
+ return new StepResponse(response)
+ }
+)
+```
+
+You create a step using `createStep` from the Workflows SDK. It accepts two parameters:
+
+1. The step's name, which is `get-magento-products`.
+2. An async function that executes the step's logic. The function receives two parameters:
+ - The input data for the step, which in this case is the pagination parameters.
+ - An object holding the workflow's context, including the [Medusa Container](https://docs.medusajs.com/docslearn/fundamentals/medusa-container/index.html.md) that allows you to resolve framework and commerce tools.
+
+In the step function, you resolve the Magento Module's service from the container, then use its `getProducts` method to retrieve the products from Magento.
+
+Steps that return data must return them in a `StepResponse` instance. The `StepResponse` constructor accepts as a parameter the data to return.
+
+### Create migrateProductsFromMagentoWorkflow
+
+You'll now create the workflow that migrates products from Magento using the step you created and steps from Medusa's `@medusajs/medusa/core-flows` package.
+
+In your plugin, create the file `src/workflows/migrate-products-from-magento.ts` with the following content:
+
+
+
+```ts title="src/workflows/migrate-products-from-magento.ts"
+import {
+ createWorkflow, transform, WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ CreateProductWorkflowInputDTO, UpsertProductDTO,
+} from "@medusajs/framework/types"
+import {
+ createProductsWorkflow,
+ updateProductsWorkflow,
+ useQueryGraphStep,
+} from "@medusajs/medusa/core-flows"
+import { getMagentoProductsStep } from "./steps/get-magento-products"
+
+type MigrateProductsFromMagentoWorkflowInput = {
+ currentPage: number
+ pageSize: number
+}
+
+export const migrateProductsFromMagentoWorkflowId =
+ "migrate-products-from-magento"
+
+export const migrateProductsFromMagentoWorkflow = createWorkflow(
+ {
+ name: migrateProductsFromMagentoWorkflowId,
+ retentionTime: 10000,
+ store: true,
+ },
+ (input: MigrateProductsFromMagentoWorkflowInput) => {
+ const { pagination, products, attributes } = getMagentoProductsStep(
+ input
+ )
+ // TODO prepare data to create and update products
+ }
+)
+```
+
+You create a workflow using `createWorkflow` from the Workflows SDK. It accepts two parameters:
+
+1. An object with the workflow's configuration, including the name and whether to store the workflow's executions. You enable storing the workflow execution so that you can view it later in the Medusa Admin dashboard.
+2. A worflow constructor function, which holds the workflow's implementation. The function receives the input data for the workflow, which is the pagination parameters.
+
+In the workflow constructor function, you use the `getMagentoProductsStep` step to retrieve the products from Magento, passing it the pagination parameters from the workflow's input.
+
+Next, you'll retrieve the Medusa store details and shipping profiles. These are necessary to prepare the data of the products to create or update.
+
+Replace the `TODO` in the workflow function with the following:
+
+```ts title="src/workflows/migrate-products-from-magento.ts"
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: ["supported_currencies.*", "default_sales_channel_id"],
+ pagination: {
+ take: 1,
+ skip: 0,
+ },
+})
+
+const { data: shippingProfiles } = useQueryGraphStep({
+ entity: "shipping_profile",
+ fields: ["id"],
+ pagination: {
+ take: 1,
+ skip: 0,
+ },
+}).config({ name: "get-shipping-profiles" })
+
+// TODO retrieve existing products
+```
+
+You use the `useQueryGraphStep` step to retrieve the store details and shipping profiles. `useQueryGraphStep` is a Medusa step that wraps [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), allowing you to use it in a workflow. Query is a tool that retrieves data across modules.
+
+Whe retrieving the store details, you specifically retrieve its supported currencies and default sales channel ID. You'll associate the products with the store's default sales channel, and set their variant prices in the supported currencies. You'll also associate the products with a shipping profile.
+
+Next, you'll retrieve products that were previously migrated from Magento to determine which products to create or update. Replace the `TODO` with the following:
+
+```ts title="src/workflows/migrate-products-from-magento.ts"
+const externalIdFilters = transform({
+ products,
+}, (data) => {
+ return data.products.map((product) => product.id.toString())
+})
+
+const { data: existingProducts } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id", "external_id", "variants.id", "variants.metadata"],
+ filters: {
+ external_id: externalIdFilters,
+ },
+}).config({ name: "get-existing-products" })
+
+// TODO prepare products to create or update
+```
+
+Since the Medusa application creates an internal representation of the workflow's constructor function, you can't manipulate data directly, as variables have no value while creating the internal representation.
+
+Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md) documentation to learn more about the workflow constructor function's constraints.
+
+Instead, you can manipulate data in a workflow's constructor function using `transform` from the Workflows SDK. `transform` is a function that accepts two parameters:
+
+- The data to transform, which in this case is the Magento products.
+- A function that transforms the data. The function receives the data passed in the first parameter and returns the transformed data.
+
+In the transformation function, you return the IDs of the Magento products. Then, you use the `useQueryGraphStep` to retrieve products in the Medusa application that have an `external_id` property matching the IDs of the Magento products. You'll use this property to store the IDs of the products in Magento.
+
+Next, you'll prepare the data to create and update the products. Replace the `TODO` in the workflow function with the following:
+
+```ts title="src/workflows/migrate-products-from-magento.ts" highlights={prepareHighlights}
+const {
+ productsToCreate,
+ productsToUpdate,
+} = transform({
+ products,
+ attributes,
+ stores,
+ shippingProfiles,
+ existingProducts,
+}, (data) => {
+ const productsToCreate = new Map()
+ const productsToUpdate = new Map()
+
+ data.products.forEach((magentoProduct) => {
+ const productData: CreateProductWorkflowInputDTO | UpsertProductDTO = {
+ title: magentoProduct.name,
+ description: magentoProduct.custom_attributes.find(
+ (attr) => attr.attribute_code === "description"
+ )?.value,
+ status: magentoProduct.status === 1 ? "published" : "draft",
+ handle: magentoProduct.custom_attributes.find(
+ (attr) => attr.attribute_code === "url_key"
+ )?.value,
+ external_id: magentoProduct.id.toString(),
+ thumbnail: magentoProduct.media_gallery_entries.find(
+ (entry) => entry.types.includes("thumbnail")
+ )?.file,
+ sales_channels: [{
+ id: data.stores[0].default_sales_channel_id,
+ }],
+ shipping_profile_id: data.shippingProfiles[0].id,
+ }
+ const existingProduct = data.existingProducts.find((p) => p.external_id === productData.external_id)
+
+ if (existingProduct) {
+ productData.id = existingProduct.id
+ }
+
+ productData.options = magentoProduct.extension_attributes.configurable_product_options?.map((option) => {
+ const attribute = data.attributes.find((attr) => attr.attribute_id === parseInt(option.attribute_id))
+ return {
+ title: option.label,
+ values: attribute?.options.filter((opt) => {
+ return option.values.find((v) => v.value_index === parseInt(opt.value))
+ }).map((opt) => opt.label) || [],
+ }
+ }) || []
+
+ productData.variants = magentoProduct.children?.map((child) => {
+ const childOptions: Record = {}
+
+ child.custom_attributes.forEach((attr) => {
+ const attrData = data.attributes.find((a) => a.attribute_code === attr.attribute_code)
+ if (!attrData) {
+ return
+ }
+
+ childOptions[attrData.default_frontend_label] = attrData.options.find((opt) => opt.value === attr.value)?.label || ""
+ })
+
+ const variantExternalId = child.id.toString()
+ const existingVariant = existingProduct.variants.find((v) => v.metadata.external_id === variantExternalId)
+
+ return {
+ title: child.name,
+ sku: child.sku,
+ options: childOptions,
+ prices: data.stores[0].supported_currencies.map(({ currency_code }) => {
+ return {
+ amount: child.price,
+ currency_code,
+ }
+ }),
+ metadata: {
+ external_id: variantExternalId,
+ },
+ id: existingVariant?.id,
+ }
+ })
+
+ productData.images = magentoProduct.media_gallery_entries.filter((entry) => !entry.types.includes("thumbnail")).map((entry) => {
+ return {
+ url: entry.file,
+ metadata: {
+ external_id: entry.id.toString(),
+ },
+ }
+ })
+
+ if (productData.id) {
+ productsToUpdate.set(existingProduct.id, productData)
+ } else {
+ productsToCreate.set(productData.external_id!, productData)
+ }
+ })
+
+ return {
+ productsToCreate: Array.from(productsToCreate.values()),
+ productsToUpdate: Array.from(productsToUpdate.values()),
+ }
+})
+
+// TODO create and update products
+```
+
+You use `transform` again to prepare the data to create and update the products in the Medusa application. For each Magento product, you map its equivalent Medusa product's data:
+
+- You set the product's general details, such as the title, description, status, handle, external ID, and thumbnail using the Magento product's data and custom attributes.
+- You associate the product with the default sales channel and shipping profile retrieved previously.
+- You map the Magento product's configurable product options to Medusa product options. In Medusa, a product's option has a label, such as "Color", and values, such as "Red". To map the option values, you use the attributes retrieved from Magento.
+- You map the Magento product's children to Medusa product variants. For the variant options, you pass an object whose keys is the option's label, such as "Color", and values is the option's value, such as "Red". For the prices, you set the variant's price based on the Magento child's price for every supported currency in the Medusa store. Also, you set the Magento child product's ID in the Medusa variant's `metadata.external_id` property.
+- You map the Magento product's media gallery entries to Medusa product images. You filter out the thumbnail image and set the URL and the Magento image's ID in the Medusa image's `metadata.external_id` property.
+
+In addition, you use the existing products retrieved in the previous step to determine whether a product should be created or updated. If there's an existing product whose `external_id` matches the ID of the magento product, you set the existing product's ID in the `id` property of the product to be updated. You also do the same for its variants.
+
+Finally, you return the products to create and update.
+
+The last steps of the workflow is to create and update the products. Replace the `TODO` in the workflow function with the following:
+
+```ts title="src/workflows/migrate-products-from-magento.ts"
+createProductsWorkflow.runAsStep({
+ input: {
+ products: productsToCreate,
+ },
+})
+
+updateProductsWorkflow.runAsStep({
+ input: {
+ products: productsToUpdate,
+ },
+})
+
+return new WorkflowResponse(pagination)
+```
+
+You use the `createProductsWorkflow` and `updateProductsWorkflow` workflows from Medusa's `@medusajs/medusa/core-flows` package to create and update the products in the Medusa application.
+
+Workflows must return an instance of `WorkflowResponse`, passing as a parameter the data to return to the workflow's executor. This workflow returns the pagination parameters, allowing you to paginate the product migration process.
+
+You can now use this workflow to migrate products from Magento to Medusa. You'll learn how to use it in the next steps.
+
+***
+
+## Step 6: Schedule Product Migration
+
+There are many ways to execute tasks asynchronously in Medusa, such as [scheduling a job](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md) or [handling emitted events](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md).
+
+In this guide, you'll learn how to schedule the product migration at a specified interval using a scheduled job. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime.
+
+Refer to the [Scheduled Jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md) documentation to learn more about scheduled jobs.
+
+To create a scheduled job, in your plugin, create the file `src/jobs/migrate-magento.ts` with the following content:
+
+
+
+```ts title="src/jobs/migrate-magento.ts"
+import { MedusaContainer } from "@medusajs/framework/types"
+import { migrateProductsFromMagentoWorkflow } from "../workflows"
+
+export default async function migrateMagentoJob(
+ container: MedusaContainer
+) {
+ const logger = container.resolve("logger")
+ logger.info("Migrating products from Magento...")
+
+ let currentPage = 0
+ const pageSize = 100
+ let totalCount = 0
+
+ do {
+ currentPage++
+
+ const {
+ result: pagination,
+ } = await migrateProductsFromMagentoWorkflow(container).run({
+ input: {
+ currentPage,
+ pageSize,
+ },
+ })
+
+ totalCount = pagination.total_count
+ } while (currentPage * pageSize < totalCount)
+
+ logger.info("Finished migrating products from Magento")
+}
+
+export const config = {
+ name: "migrate-magento-job",
+ schedule: "0 0 * * *",
+}
+```
+
+A scheduled job file must export:
+
+- An asynchronous function that executes the job's logic. The function receives the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md) as a parameter.
+- An object with the job's configuration, including the name and the schedule. The schedule is a cron job pattern as a string.
+
+In the job function, you resolve the [logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md) from the container to log messages. Then, you paginate the product migration process by running the `migrateProductsFromMagentoWorkflow` workflow at each page until you've migrated all products. You use the pagination result returned by the workflow to determine whether there are more products to migrate.
+
+Based on the job's configurations, the Medusa application will run the job at midnight every day.
+
+### Test it Out
+
+To test out this scheduled job, first, change the configuration to run the job every minute:
+
+```ts title="src/jobs/migrate-magento.ts"
+export const config = {
+ // ...
+ schedule: "* * * * *",
+}
+```
+
+Then, make sure to run the `plugin:develop` command in the plugin if you haven't already:
+
+```bash
+npx medusa plugin:develop
+```
+
+This ensures that the plugin's latest changes are reflected in the Medusa application.
+
+Finally, start the Medusa application that the plugin is installed in:
+
+```bash npm2yarn
+npm run dev
+```
+
+After a minute, you'll see a message in the terminal indicating that the migration started:
+
+```plain title="Terminal"
+info: Migrating products from Magento...
+```
+
+Once the migration is done, you'll see the following message:
+
+```plain title="Terminal"
+info: Finished migrating products from Magento
+```
+
+To confirm that the products were migrated, open the Medusa Admin dashboard at `http://localhost:9000/app` and log in. Then, click on Products in the sidebar. You'll see your magento products in the list of products.
+
+
+
+***
+
+## Next Steps
+
+You've now implemented the logic to migrate products from Magento to Medusa. You can re-use the plugin across Medusa applications. You can also expand on the plugin to:
+
+- Migrate other entities, such as orders, customers, and categories. Migrating other entities follows the same pattern as migrating products, using workflows and scheduled jobs. You only need to format the data to be migrated as needed.
+- Allow triggering migrations from the Medusa Admin dashboard using [Admin Customizations](https://docs.medusajs.com/docs/learn/fundamentals/admin/index.html.md). This feature is available in the [Example Repository](https://github.com/medusajs/example-repository/tree/main/src/admin).
+
+If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
+
+
# Integrate Algolia (Search) with Medusa
In this tutorial, you'll learn how to integrate Medusa with Algolia.
@@ -42874,1805 +44690,6 @@ If you're new to Medusa, check out the [main documentation](https://docs.medusaj
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-# How to Build Magento Data Migration Plugin
-
-In this tutorial, you'll learn how to build a [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) that migrates data, such as products, from Magento to Medusa.
-
-Magento is known for its customization capabilities. However, its monolithic architecture imposes limitations on business requirements, often forcing development teams to implement hacky workarounds. Over time, these customizations become challenging to maintain, especially as the business scales, leading to increased technical debt and slower feature delivery.
-
-Medusa's modular architecture allows you to build a custom digital commerce platform that meets your business requirements without the limitations of a monolithic system. By migrating from Magento to Medusa, you can take advantage of Medusa's modern technology stack to build a scalable and flexible commerce platform that grows with your business.
-
-By following this tutorial, you'll create a Medusa plugin that migrates data from a Magento server to a Medusa application in minimal time. You can re-use this plugin across multiple Medusa applications, allowing you to adopt Medusa across your projects.
-
-## Summary
-
-### Prerequisites
-
-
-
-This tutorial will teach you how to:
-
-- Install and set up a Medusa application project.
-- Install and set up a Medusa plugin.
-- Implement a Magento Module in the plugin to connect to Magento's APIs and retrieve products.
- - This guide will only focus on migrating product data from Magento to Medusa. You can extend the implementation to migrate other data, such as customers, orders, and more.
-- Trigger data migration from Magento to Medusa in a scheduled job.
-
-You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer.
-
-
-
-[Example Repository](https://github.com/medusajs/examples/tree/main/migrate-from-magento): Find the full code of the guide in this repository. The repository also includes additional features, such as triggering migrations from the Medusa Admin dashboard.
-
-***
-
-## Step 1: Install a Medusa Application
-
-You'll first install a Medusa application that exposes core commerce features through REST APIs. You'll later install the Magento plugin in this application to test it out.
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-- [PostgreSQL](https://www.postgresql.org/download/)
-
-Start by installing the Medusa application on your machine with the following command:
-
-```bash
-npx create-medusa-app@latest
-```
-
-You'll be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md).
-
-Afterward, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
-
-The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Refer to the [Medusa Architecture](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md) documentation to learn more.
-
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterward, you can log in with the new user and explore the dashboard.
-
-Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
-
-***
-
-## Step 2: Install a Medusa Plugin Project
-
-A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. You can add in the plugin [API Routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), and other customizations, as you'll see in this guide. Afterward, you can test it out locally in a Medusa application, then publish it to npm to install and use it in any Medusa application.
-
-Refer to the [Plugins](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) documentation to learn more about plugins.
-
-A Medusa plugin is set up in a different project, giving you the flexibility in building and publishing it, while providing you with the tools to test it out locally in a Medusa application.
-
-To create a new Medusa plugin project, run the following command in a directory different than that of the Medusa application:
-
-```bash npm2yarn
-npx create-medusa-app@latest medusa-plugin-magento --plugin
-```
-
-Where `medusa-plugin-magento` is the name of the plugin's directory and the name set in the plugin's `package.json`. So, if you wish to publish it to NPM later under a different name, you can change it here in the command or later in `package.json`.
-
-Once the installation process is done, a new directory named `medusa-plugin-magento` will be created with the plugin project files.
-
-
-
-***
-
-## Step 3: Set up Plugin in Medusa Application
-
-Before you start your development, you'll set up the plugin in the Medusa application you installed in the first step. This will allow you to test the plugin during your development process.
-
-In the plugin's directory, run the following command to publish the plugin to the local package registry:
-
-```bash title="Plugin project"
-npx medusa plugin:publish
-```
-
-This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
-
-Next, you'll install the plugin in the Medusa application from the local registry.
-
-If you've installed your Medusa project before v2.3.1, you must install [yalc](https://github.com/wclr/yalc) as a development dependency first.
-
-Run the following command in the Medusa application's directory to install the plugin:
-
-```bash title="Medusa application"
-npx medusa plugin:add medusa-plugin-magento
-```
-
-This command installs the plugin in the Medusa application from the local package registry.
-
-Next, register the plugin in the `medusa-config.ts` file of the Medusa application:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "medusa-plugin-magento",
- options: {
- // TODO add options
- },
- },
- ],
-})
-```
-
-You add the plugin to the array of plugins. Later, you'll pass options useful to retrieve data from Magento.
-
-Finally, to ensure your plugin's changes are constantly published to the local registry, simplifying your testing process, keep the following command running in the plugin project during development:
-
-```bash title="Plugin project"
-npx medusa plugin:develop
-```
-
-***
-
-## Step 4: Implement Magento Module
-
-To connect to external applications in Medusa, you create a custom module. A module is a reusable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
-
-In this step, you'll create a Magento Module in the Magento plugin that connects to a Magento server's REST APIs and retrieves data, such as products.
-
-Refer to the [Modules](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) documentation to learn more about modules.
-
-### Create Module Directory
-
-A module is created under the `src/modules` directory of your plugin. So, create the directory `src/modules/magento`.
-
-
-
-### Create Module's Service
-
-You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to external systems or the database, which is useful if your module defines tables in the database.
-
-In this section, you'll create the Magento Module's service that connects to Magento's REST APIs and retrieves data.
-
-Start by creating the file `src/modules/magento/service.ts` in the plugin with the following content:
-
-
-
-```ts title="src/modules/magento/service.ts"
-type Options = {
- baseUrl: string
- storeCode?: string
- username: string
- password: string
- migrationOptions?: {
- imageBaseUrl?: string
- }
-}
-
-export default class MagentoModuleService {
- private options: Options
-
- constructor({}, options: Options) {
- this.options = {
- ...options,
- storeCode: options.storeCode || "default",
- }
- }
-}
-```
-
-You create a `MagentoModuleService` that has an `options` property to store the module's options. These options include:
-
-- `baseUrl`: The base URL of the Magento server.
-- `storeCode`: The store code of the Magento store, which is `default` by default.
-- `username`: The username of a Magento admin user to authenticate with the Magento server.
-- `password`: The password of the Magento admin user.
-- `migrationOptions`: Additional options useful for migrating data, such as the base URL to use for product images.
-
-The service's constructor accepts as a first parameter the [Module Container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md), which allows you to access resources available for the module. As a second parameter, it accepts the module's options.
-
-### Add Authentication Logic
-
-To authenticate with the Magento server, you'll add a method to the service that retrieves an access token from Magento using the username and password in the options. This access token is used in subsequent requests to the Magento server.
-
-First, add the following property to the `MagentoModuleService` class:
-
-```ts title="src/modules/magento/service.ts"
-export default class MagentoModuleService {
- private accessToken: {
- token: string
- expiresAt: Date
- }
- // ...
-}
-```
-
-You add an `accessToken` property to store the access token and its expiration date. The access token Magento returns expires after four hours, so you store the expiration date to know when to refresh the token.
-
-Next, add the following `authenticate` method to the `MagentoModuleService` class:
-
-```ts title="src/modules/magento/service.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-
-export default class MagentoModuleService {
- // ...
- async authenticate() {
- const response = await fetch(`${this.options.baseUrl}/rest/${this.options.storeCode}/V1/integration/admin/token`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ username: this.options.username, password: this.options.password }),
- })
-
- const token = await response.text()
-
- if (!response.ok) {
- throw new MedusaError(MedusaError.Types.UNAUTHORIZED, `Failed to authenticate with Magento: ${token}`)
- }
-
- this.accessToken = {
- token: token.replaceAll("\"", ""),
- expiresAt: new Date(Date.now() + 4 * 60 * 60 * 1000), // 4 hours in milliseconds
- }
- }
-}
-```
-
-You create an `authenticate` method that sends a POST request to the Magento server's `/rest/{storeCode}/V1/integration/admin/token` endpoint, passing the username and password in the request body.
-
-If the request is successful, you store the access token and its expiration date in the `accessToken` property. If the request fails, you throw a `MedusaError` with the error message returned by Magento.
-
-Lastly, add an `isAccessTokenExpired` method that checks if the access token has expired:
-
-```ts title="src/modules/magento/service.ts"
-export default class MagentoModuleService {
- // ...
- async isAccessTokenExpired(): Promise {
- return !this.accessToken || this.accessToken.expiresAt < new Date()
- }
-}
-```
-
-In the `isAccessTokenExpired` method, you return a boolean indicating whether the access token has expired. You'll use this in later methods to check if you need to refresh the access token.
-
-### Retrieve Products from Magento
-
-Next, you'll add a method that retrieves products from Magento. Due to limitations in Magento's API that makes it difficult to differentiate between simple products that don't belong to a configurable product and those that do, you'll only retrieve configurable products and their children. You'll also retrieve the configurable attributes of the product, such as color and size.
-
-First, you'll add some types to represent a Magento product and its attributes. Create the file `src/modules/magento/types.ts` in the plugin with the following content:
-
-
-
-```ts title="src/modules/magento/types.ts"
-export type MagentoProduct = {
- id: number
- sku: string
- name: string
- price: number
- status: number
- // not handling other types
- type_id: "simple" | "configurable"
- created_at: string
- updated_at: string
- extension_attributes: {
- category_links: {
- category_id: string
- }[]
- configurable_product_links?: number[]
- configurable_product_options?: {
- id: number
- attribute_id: string
- label: string
- position: number
- values: {
- value_index: number
- }[]
- }[]
- }
- media_gallery_entries: {
- id: number
- media_type: string
- label: string
- position: number
- disabled: boolean
- types: string[]
- file: string
- }[]
- custom_attributes: {
- attribute_code: string
- value: string
- }[]
- // added by module
- children?: MagentoProduct[]
-}
-
-export type MagentoAttribute = {
- attribute_code: string
- attribute_id: number
- default_frontend_label: string
- options: {
- label: string
- value: string
- }[]
-}
-
-export type MagentoPagination = {
- search_criteria: {
- filter_groups: [],
- page_size: number
- current_page: number
- }
- total_count: number
-}
-
-export type MagentoPaginatedResponse = {
- items: TData[]
-} & MagentoPagination
-```
-
-You define the following types:
-
-- `MagentoProduct`: Represents a product in Magento.
-- `MagentoAttribute`: Represents an attribute in Magento.
-- `MagentoPagination`: Represents the pagination information returned by Magento's API.
-- `MagentoPaginatedResponse`: Represents a paginated response from Magento's API for a specific item type, such as products.
-
-Next, add the `getProducts` method to the `MagentoModuleService` class:
-
-```ts title="src/modules/magento/service.ts"
-export default class MagentoModuleService {
- // ...
- async getProducts(options?: {
- currentPage?: number
- pageSize?: number
- }): Promise<{
- products: MagentoProduct[]
- attributes: MagentoAttribute[]
- pagination: MagentoPagination
- }> {
- const { currentPage = 1, pageSize = 100 } = options || {}
- const getAccessToken = await this.isAccessTokenExpired()
- if (getAccessToken) {
- await this.authenticate()
- }
-
- // TODO prepare query params
- }
-}
-```
-
-The `getProducts` method receives an optional `options` object with the `currentPage` and `pageSize` properties. So far, you check if the access token has expired and, if so, retrieve a new one using the `authenticate` method.
-
-Next, you'll prepare the query parameters to pass in the request that retrieves products. Replace the `TODO` with the following:
-
-```ts title="src/modules/magento/service.ts"
-const searchQuery = new URLSearchParams()
-// pass pagination parameters
-searchQuery.append(
- "searchCriteria[currentPage]",
- currentPage?.toString() || "1"
-)
-searchQuery.append(
- "searchCriteria[pageSize]",
- pageSize?.toString() || "100"
-)
-
-// retrieve only configurable products
-searchQuery.append(
- "searchCriteria[filter_groups][1][filters][0][field]",
- "type_id"
-)
-searchQuery.append(
- "searchCriteria[filter_groups][1][filters][0][value]",
- "configurable"
-)
-searchQuery.append(
- "searchCriteria[filter_groups][1][filters][0][condition_type]",
- "in"
-)
-
-// TODO send request to retrieve products
-```
-
-You create a `searchQuery` object to store the query parameters to pass in the request. Then, you add the pagination parameters and the filter to retrieve only configurable products.
-
-Next, you'll send the request to retrieve products from Magento. Replace the `TODO` with the following:
-
-```ts title="src/modules/magento/service.ts"
-const { items: products, ...pagination }: MagentoPaginatedResponse = await fetch(
- `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/products?${searchQuery}`,
- {
- headers: {
- "Authorization": `Bearer ${this.accessToken.token}`,
- },
- }
-).then((res) => res.json())
-.catch((err) => {
- console.log(err)
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- `Failed to get products from Magento: ${err.message}`
- )
-})
-
-// TODO prepare products
-```
-
-You send a `GET` request to the Magento server's `/rest/{storeCode}/V1/products` endpoint, passing the query parameters in the URL. You also pass the access token in the `Authorization` header.
-
-Next, you'll prepare the retrieved products by retrieving their children, configurable attributes, and modifying their image URLs. Replace the `TODO` with the following:
-
-```ts title="src/modules/magento/service.ts"
-const attributeIds: string[] = []
-
-await promiseAll(
- products.map(async (product) => {
- // retrieve its children
- product.children = await fetch(
- `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/configurable-products/${product.sku}/children`,
- {
- headers: {
- "Authorization": `Bearer ${this.accessToken.token}`,
- },
- }
- ).then((res) => res.json())
- .catch((err) => {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- `Failed to get product children from Magento: ${err.message}`
- )
- })
-
- product.media_gallery_entries = product.media_gallery_entries.map(
- (entry) => ({
- ...entry,
- file: `${this.options.migrationOptions?.imageBaseUrl}${entry.file}`,
- }
- ))
-
- attributeIds.push(...(
- product.extension_attributes.configurable_product_options?.map(
- (option) => option.attribute_id) || []
- )
- )
- })
-)
-
-// TODO retrieve attributes
-```
-
-You loop over the retrieved products and retrieve their children using the `/rest/{storeCode}/V1/configurable-products/{sku}/children` endpoint. You also modify the image URLs to use the base URL in the migration options, if provided.
-
-In addition, you store the IDs of the configurable products' attributes in the `attributeIds` array. You'll add a method that retrieves these attributes.
-
-Add the new method `getAttributes` to the `MagentoModuleService` class:
-
-```ts title="src/modules/magento/service.ts"
-export default class MagentoModuleService {
- // ...
- async getAttributes({
- ids,
- }: {
- ids: string[]
- }): Promise {
- const getAccessToken = await this.isAccessTokenExpired()
- if (getAccessToken) {
- await this.authenticate()
- }
-
- // filter by attribute IDs
- const searchQuery = new URLSearchParams()
- searchQuery.append(
- "searchCriteria[filter_groups][0][filters][0][field]",
- "attribute_id"
- )
- searchQuery.append(
- "searchCriteria[filter_groups][0][filters][0][value]",
- ids.join(",")
- )
- searchQuery.append(
- "searchCriteria[filter_groups][0][filters][0][condition_type]",
- "in"
- )
-
- const {
- items: attributes,
- }: MagentoPaginatedResponse = await fetch(
- `${this.options.baseUrl}/rest/${this.options.storeCode}/V1/products/attributes?${searchQuery}`,
- {
- headers: {
- "Authorization": `Bearer ${this.accessToken.token}`,
- },
- }
- ).then((res) => res.json())
- .catch((err) => {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- `Failed to get attributes from Magento: ${err.message}`
- )
- })
-
- return attributes
- }
-}
-```
-
-The `getAttributes` method receives an object with the `ids` property, which is an array of attribute IDs. You check if the access token has expired and, if so, retrieve a new one using the `authenticate` method.
-
-Next, you prepare the query parameters to pass in the request to retrieve attributes. You send a `GET` request to the Magento server's `/rest/{storeCode}/V1/products/attributes` endpoint, passing the query parameters in the URL. You also pass the access token in the `Authorization` header.
-
-Finally, you return the retrieved attributes.
-
-Now, go back to the `getProducts` method and replace the `TODO` with the following:
-
-```ts title="src/modules/magento/service.ts"
-const attributes = await this.getAttributes({ ids: attributeIds })
-
-return { products, attributes, pagination }
-```
-
-You retrieve the configurable products' attributes using the `getAttributes` method and return the products, attributes, and pagination information.
-
-You'll use this method in a later step to retrieve products from Magento.
-
-### Export Module Definition
-
-The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service.
-
-So, create the file `src/modules/magento/index.ts` with the following content:
-
-
-
-```ts title="src/modules/magento/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import MagentoModuleService from "./service"
-
-export const MAGENTO_MODULE = "magento"
-
-export default Module(MAGENTO_MODULE, {
- service: MagentoModuleService,
-})
-```
-
-You use the `Module` function from the Modules SDK to create the module's definition. It accepts two parameters:
-
-1. The module's name, which is `magento`.
-2. An object with a required property `service` indicating the module's service.
-
-You'll later use the module's service to retrieve products from Magento.
-
-### Pass Options to Plugin
-
-As mentioned earlier when you registered the plugin in the Medusa Application's `medusa-config.ts` file, you can pass options to the plugin. These options are then passed to the modules in the plugin.
-
-So, add the following options to the plugin's registration in the `medusa-config.ts` file of the Medusa application:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "medusa-plugin-magento",
- options: {
- baseUrl: process.env.MAGENTO_BASE_URL,
- username: process.env.MAGENTO_USERNAME,
- password: process.env.MAGENTO_PASSWORD,
- migrationOptions: {
- imageBaseUrl: process.env.MAGENTO_IMAGE_BASE_URL,
- },
- },
- },
- ],
-})
-```
-
-You pass the options that you defined in the `MagentoModuleService`. Make sure to also set their environment variables in the `.env` file:
-
-```bash
-MAGENTO_BASE_URL=https://magento.example.com
-MAGENTO_USERNAME=admin
-MAGENTO_PASSWORD=password
-MAGENTO_IMAGE_BASE_URL=https://magento.example.com/pub/media/catalog/product
-```
-
-Where:
-
-- `MAGENTO_BASE_URL`: The base URL of the Magento server. It can also be a local URL, such as `http://localhost:8080`.
-- `MAGENTO_USERNAME`: The username of a Magento admin user to authenticate with the Magento server.
-- `MAGENTO_PASSWORD`: The password of the Magento admin user.
-- `MAGENTO_IMAGE_BASE_URL`: The base URL to use for product images. Magento stores product images in the `pub/media/catalog/product` directory, so you can reference them directly or use a CDN URL. If the URLs of product images in the Medusa server already have a different base URL, you can omit this option.
-
-Medusa supports integrating third-party services, such as [S3](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/file/s3/index.html.md), in a File Module Provider. Refer to the [File Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/architectural-modules/file/index.html.md) documentation to find other module providers and how to create a custom provider.
-
-You can now use the Magento Module to migrate data, which you'll do in the next steps.
-
-***
-
-## Step 5: Build Product Migration Workflow
-
-In this section, you'll add the feature to migrate products from Magento to Medusa. To implement this feature, you'll use a workflow.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an API route or a scheduled job.
-
-By implementing the migration feature in a workflow, you ensure that the data remains consistent and that the migration process can be rolled back if an error occurs.
-
-Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) documentation to learn more about workflows.
-
-### Workflow Steps
-
-The workflow you'll create will have the following steps:
-
-- [getMagentoProductsStep](#getMagentoProductsStep): Retrieve products from Magento using the Magento Module.
-- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve Medusa store details, which you'll need when creating the products.
-- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve a shipping profile, which you'll associate the created products with.
-- [useQueryGraphStep](https://docs.medusajs.com/references/helper-steps/useQueryGraphStep/index.html.md): Retrieve Magento products that are already in Medusa to update them, instead of creating them.
-- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md): Create products in the Medusa application.
-- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md): Update existing products in the Medusa application.
-
-You only need to implement the `getMagentoProductsStep` step, which retrieves the products from Magento. The other steps and workflows are provided by Medusa's `@medusajs/medusa/core-flows` package.
-
-### getMagentoProductsStep
-
-The first step of the workflow retrieves and returns the products from Magento.
-
-In your plugin, create the file `src/workflows/steps/get-magento-products.ts` with the following content:
-
-
-
-```ts title="src/workflows/steps/get-magento-products.ts"
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-import { MAGENTO_MODULE } from "../../modules/magento"
-import MagentoModuleService from "../../modules/magento/service"
-
-type GetMagentoProductsInput = {
- currentPage: number
- pageSize: number
-}
-
-export const getMagentoProductsStep = createStep(
- "get-magento-products",
- async ({ currentPage, pageSize }: GetMagentoProductsInput, { container }) => {
- const magentoModuleService: MagentoModuleService =
- container.resolve(MAGENTO_MODULE)
-
- const response = await magentoModuleService.getProducts({
- currentPage,
- pageSize,
- })
-
- return new StepResponse(response)
- }
-)
-```
-
-You create a step using `createStep` from the Workflows SDK. It accepts two parameters:
-
-1. The step's name, which is `get-magento-products`.
-2. An async function that executes the step's logic. The function receives two parameters:
- - The input data for the step, which in this case is the pagination parameters.
- - An object holding the workflow's context, including the [Medusa Container](https://docs.medusajs.com/docslearn/fundamentals/medusa-container/index.html.md) that allows you to resolve framework and commerce tools.
-
-In the step function, you resolve the Magento Module's service from the container, then use its `getProducts` method to retrieve the products from Magento.
-
-Steps that return data must return them in a `StepResponse` instance. The `StepResponse` constructor accepts as a parameter the data to return.
-
-### Create migrateProductsFromMagentoWorkflow
-
-You'll now create the workflow that migrates products from Magento using the step you created and steps from Medusa's `@medusajs/medusa/core-flows` package.
-
-In your plugin, create the file `src/workflows/migrate-products-from-magento.ts` with the following content:
-
-
-
-```ts title="src/workflows/migrate-products-from-magento.ts"
-import {
- createWorkflow, transform, WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import {
- CreateProductWorkflowInputDTO, UpsertProductDTO,
-} from "@medusajs/framework/types"
-import {
- createProductsWorkflow,
- updateProductsWorkflow,
- useQueryGraphStep,
-} from "@medusajs/medusa/core-flows"
-import { getMagentoProductsStep } from "./steps/get-magento-products"
-
-type MigrateProductsFromMagentoWorkflowInput = {
- currentPage: number
- pageSize: number
-}
-
-export const migrateProductsFromMagentoWorkflowId =
- "migrate-products-from-magento"
-
-export const migrateProductsFromMagentoWorkflow = createWorkflow(
- {
- name: migrateProductsFromMagentoWorkflowId,
- retentionTime: 10000,
- store: true,
- },
- (input: MigrateProductsFromMagentoWorkflowInput) => {
- const { pagination, products, attributes } = getMagentoProductsStep(
- input
- )
- // TODO prepare data to create and update products
- }
-)
-```
-
-You create a workflow using `createWorkflow` from the Workflows SDK. It accepts two parameters:
-
-1. An object with the workflow's configuration, including the name and whether to store the workflow's executions. You enable storing the workflow execution so that you can view it later in the Medusa Admin dashboard.
-2. A worflow constructor function, which holds the workflow's implementation. The function receives the input data for the workflow, which is the pagination parameters.
-
-In the workflow constructor function, you use the `getMagentoProductsStep` step to retrieve the products from Magento, passing it the pagination parameters from the workflow's input.
-
-Next, you'll retrieve the Medusa store details and shipping profiles. These are necessary to prepare the data of the products to create or update.
-
-Replace the `TODO` in the workflow function with the following:
-
-```ts title="src/workflows/migrate-products-from-magento.ts"
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: ["supported_currencies.*", "default_sales_channel_id"],
- pagination: {
- take: 1,
- skip: 0,
- },
-})
-
-const { data: shippingProfiles } = useQueryGraphStep({
- entity: "shipping_profile",
- fields: ["id"],
- pagination: {
- take: 1,
- skip: 0,
- },
-}).config({ name: "get-shipping-profiles" })
-
-// TODO retrieve existing products
-```
-
-You use the `useQueryGraphStep` step to retrieve the store details and shipping profiles. `useQueryGraphStep` is a Medusa step that wraps [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), allowing you to use it in a workflow. Query is a tool that retrieves data across modules.
-
-Whe retrieving the store details, you specifically retrieve its supported currencies and default sales channel ID. You'll associate the products with the store's default sales channel, and set their variant prices in the supported currencies. You'll also associate the products with a shipping profile.
-
-Next, you'll retrieve products that were previously migrated from Magento to determine which products to create or update. Replace the `TODO` with the following:
-
-```ts title="src/workflows/migrate-products-from-magento.ts"
-const externalIdFilters = transform({
- products,
-}, (data) => {
- return data.products.map((product) => product.id.toString())
-})
-
-const { data: existingProducts } = useQueryGraphStep({
- entity: "product",
- fields: ["id", "external_id", "variants.id", "variants.metadata"],
- filters: {
- external_id: externalIdFilters,
- },
-}).config({ name: "get-existing-products" })
-
-// TODO prepare products to create or update
-```
-
-Since the Medusa application creates an internal representation of the workflow's constructor function, you can't manipulate data directly, as variables have no value while creating the internal representation.
-
-Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md) documentation to learn more about the workflow constructor function's constraints.
-
-Instead, you can manipulate data in a workflow's constructor function using `transform` from the Workflows SDK. `transform` is a function that accepts two parameters:
-
-- The data to transform, which in this case is the Magento products.
-- A function that transforms the data. The function receives the data passed in the first parameter and returns the transformed data.
-
-In the transformation function, you return the IDs of the Magento products. Then, you use the `useQueryGraphStep` to retrieve products in the Medusa application that have an `external_id` property matching the IDs of the Magento products. You'll use this property to store the IDs of the products in Magento.
-
-Next, you'll prepare the data to create and update the products. Replace the `TODO` in the workflow function with the following:
-
-```ts title="src/workflows/migrate-products-from-magento.ts" highlights={prepareHighlights}
-const {
- productsToCreate,
- productsToUpdate,
-} = transform({
- products,
- attributes,
- stores,
- shippingProfiles,
- existingProducts,
-}, (data) => {
- const productsToCreate = new Map()
- const productsToUpdate = new Map()
-
- data.products.forEach((magentoProduct) => {
- const productData: CreateProductWorkflowInputDTO | UpsertProductDTO = {
- title: magentoProduct.name,
- description: magentoProduct.custom_attributes.find(
- (attr) => attr.attribute_code === "description"
- )?.value,
- status: magentoProduct.status === 1 ? "published" : "draft",
- handle: magentoProduct.custom_attributes.find(
- (attr) => attr.attribute_code === "url_key"
- )?.value,
- external_id: magentoProduct.id.toString(),
- thumbnail: magentoProduct.media_gallery_entries.find(
- (entry) => entry.types.includes("thumbnail")
- )?.file,
- sales_channels: [{
- id: data.stores[0].default_sales_channel_id,
- }],
- shipping_profile_id: data.shippingProfiles[0].id,
- }
- const existingProduct = data.existingProducts.find((p) => p.external_id === productData.external_id)
-
- if (existingProduct) {
- productData.id = existingProduct.id
- }
-
- productData.options = magentoProduct.extension_attributes.configurable_product_options?.map((option) => {
- const attribute = data.attributes.find((attr) => attr.attribute_id === parseInt(option.attribute_id))
- return {
- title: option.label,
- values: attribute?.options.filter((opt) => {
- return option.values.find((v) => v.value_index === parseInt(opt.value))
- }).map((opt) => opt.label) || [],
- }
- }) || []
-
- productData.variants = magentoProduct.children?.map((child) => {
- const childOptions: Record = {}
-
- child.custom_attributes.forEach((attr) => {
- const attrData = data.attributes.find((a) => a.attribute_code === attr.attribute_code)
- if (!attrData) {
- return
- }
-
- childOptions[attrData.default_frontend_label] = attrData.options.find((opt) => opt.value === attr.value)?.label || ""
- })
-
- const variantExternalId = child.id.toString()
- const existingVariant = existingProduct.variants.find((v) => v.metadata.external_id === variantExternalId)
-
- return {
- title: child.name,
- sku: child.sku,
- options: childOptions,
- prices: data.stores[0].supported_currencies.map(({ currency_code }) => {
- return {
- amount: child.price,
- currency_code,
- }
- }),
- metadata: {
- external_id: variantExternalId,
- },
- id: existingVariant?.id,
- }
- })
-
- productData.images = magentoProduct.media_gallery_entries.filter((entry) => !entry.types.includes("thumbnail")).map((entry) => {
- return {
- url: entry.file,
- metadata: {
- external_id: entry.id.toString(),
- },
- }
- })
-
- if (productData.id) {
- productsToUpdate.set(existingProduct.id, productData)
- } else {
- productsToCreate.set(productData.external_id!, productData)
- }
- })
-
- return {
- productsToCreate: Array.from(productsToCreate.values()),
- productsToUpdate: Array.from(productsToUpdate.values()),
- }
-})
-
-// TODO create and update products
-```
-
-You use `transform` again to prepare the data to create and update the products in the Medusa application. For each Magento product, you map its equivalent Medusa product's data:
-
-- You set the product's general details, such as the title, description, status, handle, external ID, and thumbnail using the Magento product's data and custom attributes.
-- You associate the product with the default sales channel and shipping profile retrieved previously.
-- You map the Magento product's configurable product options to Medusa product options. In Medusa, a product's option has a label, such as "Color", and values, such as "Red". To map the option values, you use the attributes retrieved from Magento.
-- You map the Magento product's children to Medusa product variants. For the variant options, you pass an object whose keys is the option's label, such as "Color", and values is the option's value, such as "Red". For the prices, you set the variant's price based on the Magento child's price for every supported currency in the Medusa store. Also, you set the Magento child product's ID in the Medusa variant's `metadata.external_id` property.
-- You map the Magento product's media gallery entries to Medusa product images. You filter out the thumbnail image and set the URL and the Magento image's ID in the Medusa image's `metadata.external_id` property.
-
-In addition, you use the existing products retrieved in the previous step to determine whether a product should be created or updated. If there's an existing product whose `external_id` matches the ID of the magento product, you set the existing product's ID in the `id` property of the product to be updated. You also do the same for its variants.
-
-Finally, you return the products to create and update.
-
-The last steps of the workflow is to create and update the products. Replace the `TODO` in the workflow function with the following:
-
-```ts title="src/workflows/migrate-products-from-magento.ts"
-createProductsWorkflow.runAsStep({
- input: {
- products: productsToCreate,
- },
-})
-
-updateProductsWorkflow.runAsStep({
- input: {
- products: productsToUpdate,
- },
-})
-
-return new WorkflowResponse(pagination)
-```
-
-You use the `createProductsWorkflow` and `updateProductsWorkflow` workflows from Medusa's `@medusajs/medusa/core-flows` package to create and update the products in the Medusa application.
-
-Workflows must return an instance of `WorkflowResponse`, passing as a parameter the data to return to the workflow's executor. This workflow returns the pagination parameters, allowing you to paginate the product migration process.
-
-You can now use this workflow to migrate products from Magento to Medusa. You'll learn how to use it in the next steps.
-
-***
-
-## Step 6: Schedule Product Migration
-
-There are many ways to execute tasks asynchronously in Medusa, such as [scheduling a job](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md) or [handling emitted events](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md).
-
-In this guide, you'll learn how to schedule the product migration at a specified interval using a scheduled job. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime.
-
-Refer to the [Scheduled Jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md) documentation to learn more about scheduled jobs.
-
-To create a scheduled job, in your plugin, create the file `src/jobs/migrate-magento.ts` with the following content:
-
-
-
-```ts title="src/jobs/migrate-magento.ts"
-import { MedusaContainer } from "@medusajs/framework/types"
-import { migrateProductsFromMagentoWorkflow } from "../workflows"
-
-export default async function migrateMagentoJob(
- container: MedusaContainer
-) {
- const logger = container.resolve("logger")
- logger.info("Migrating products from Magento...")
-
- let currentPage = 0
- const pageSize = 100
- let totalCount = 0
-
- do {
- currentPage++
-
- const {
- result: pagination,
- } = await migrateProductsFromMagentoWorkflow(container).run({
- input: {
- currentPage,
- pageSize,
- },
- })
-
- totalCount = pagination.total_count
- } while (currentPage * pageSize < totalCount)
-
- logger.info("Finished migrating products from Magento")
-}
-
-export const config = {
- name: "migrate-magento-job",
- schedule: "0 0 * * *",
-}
-```
-
-A scheduled job file must export:
-
-- An asynchronous function that executes the job's logic. The function receives the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md) as a parameter.
-- An object with the job's configuration, including the name and the schedule. The schedule is a cron job pattern as a string.
-
-In the job function, you resolve the [logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md) from the container to log messages. Then, you paginate the product migration process by running the `migrateProductsFromMagentoWorkflow` workflow at each page until you've migrated all products. You use the pagination result returned by the workflow to determine whether there are more products to migrate.
-
-Based on the job's configurations, the Medusa application will run the job at midnight every day.
-
-### Test it Out
-
-To test out this scheduled job, first, change the configuration to run the job every minute:
-
-```ts title="src/jobs/migrate-magento.ts"
-export const config = {
- // ...
- schedule: "* * * * *",
-}
-```
-
-Then, make sure to run the `plugin:develop` command in the plugin if you haven't already:
-
-```bash
-npx medusa plugin:develop
-```
-
-This ensures that the plugin's latest changes are reflected in the Medusa application.
-
-Finally, start the Medusa application that the plugin is installed in:
-
-```bash npm2yarn
-npm run dev
-```
-
-After a minute, you'll see a message in the terminal indicating that the migration started:
-
-```plain title="Terminal"
-info: Migrating products from Magento...
-```
-
-Once the migration is done, you'll see the following message:
-
-```plain title="Terminal"
-info: Finished migrating products from Magento
-```
-
-To confirm that the products were migrated, open the Medusa Admin dashboard at `http://localhost:9000/app` and log in. Then, click on Products in the sidebar. You'll see your magento products in the list of products.
-
-
-
-***
-
-## Next Steps
-
-You've now implemented the logic to migrate products from Magento to Medusa. You can re-use the plugin across Medusa applications. You can also expand on the plugin to:
-
-- Migrate other entities, such as orders, customers, and categories. Migrating other entities follows the same pattern as migrating products, using workflows and scheduled jobs. You only need to format the data to be migrated as needed.
-- Allow triggering migrations from the Medusa Admin dashboard using [Admin Customizations](https://docs.medusajs.com/docs/learn/fundamentals/admin/index.html.md). This feature is available in the [Example Repository](https://github.com/medusajs/example-repository/tree/main/src/admin).
-
-If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
-
-To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-
-
-# Integrate Medusa with Resend (Email Notifications)
-
-In this guide, you'll learn how to integrate Medusa with Resend.
-
-When you install a Medusa application, you get a fully-fledged commerce platform with a framework for customization. Medusa's architecture supports integrating third-party services, such as an email service, that allow you to build your unique requirements around core commerce flows.
-
-[Resend](https://resend.com/docs/introduction) is an email service with an intuitive developer experience to send emails from any application type, including Node.js servers. By integrating Resend with Medusa, you can build flows to send an email when a commerce operation is performed, such as when an order is placed.
-
-This guide will teach you how to:
-
-- Install and set up Medusa.
-- Integrate Resend into Medusa for sending emails.
-- Build a flow to send an email with Resend when a customer places an order.
-
-You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
-
-[Example Repository](https://github.com/medusajs/examples/tree/main/resend-integration): Find the full code of the guide in this repository.
-
-***
-
-## Step 1: Install a Medusa Application
-
-### Prerequisites
-
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
-- [PostgreSQL](https://www.postgresql.org/download/)
-
-Start by installing the Medusa application on your machine with the following command:
-
-```bash
-npx create-medusa-app@latest
-```
-
-You'll first be asked for the project's name. Then, when you're asked whether you want to install the Next.js storefront, choose `Y` for yes.
-
-Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name, and the Next.js storefront in a directory with the `{project-name}-storefront` name.
-
-The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more about Medusa's architecture in [this documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md).
-
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credential and submit the form. Afterwards, you can login with the new user and explore the dashboard.
-
-The Next.js storefront is also running at `http://localhost:8000`.
-
-Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help.
-
-***
-
-## Step 2: Prepare Resend Account
-
-If you don't have a Resend Account, create one on [their website](https://resend.com/emails).
-
-In addition, Resend allows you to send emails from the address `onboarding@resend.dev` only to your account's email, which is useful for development purposes. If you have a custom domain to send emails from, add it to your Resend account's domains:
-
-1. Go to Domains from the sidebar.
-2. Click on Add Domain.
-
-
-
-3\. In the form that opens, enter your domain name and select a region close to your users, then click Add.
-
-
-
-4\. In the domain's details page that opens, you'll find DNS records to add to your DNS provider. After you add them, click on Verify DNS Records. You can start sending emails from your custom domain once it's verified.
-
-The steps to add DNS records are different for each provider, so refer to your provider's documentation or knowledge base for more details.
-
-
-
-You also need an API key to connect to your Resend account from Medusa, but you'll create that one in a later section.
-
-***
-
-## Step 3: Install Resend Dependencies
-
-In this step, you'll install two packages useful for your Resend integration:
-
-1. `resend`, which is the Resend SDK:
-
-```bash npm2yarn
-npm install resend
-```
-
-2\. [react-email](https://github.com/resend/react-email), which is a package created by Resend to create email templates with React:
-
-```bash npm2yarn
-npm install @react-email/components -E
-```
-
-You'll use these packages in the next steps.
-
-***
-
-## Step 4: Create Resend Module Provider
-
-To integrate third-party services into Medusa, you create a custom module. A module is a re-usable package with functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup.
-
-Medusa's Notification Module delegates sending notifications to other modules, called module providers. In this step, you'll create a Resend Module Provider that implements sending notifications through the email channel. In later steps, you'll send email notifications with Resend when an order is placed through this provider.
-
-Learn more about modules in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
-
-### Create Module Directory
-
-A module is created under the `src/modules` directory of your Medusa application. So, create the directory `src/modules/resend`.
-
-### Create Service
-
-You define a module's functionalities in a service. A service is a TypeScript or JavaScript class that the module exports. In the service's methods, you can connect to the database, which is useful if your module defines tables in the database, or connect to a third-party service.
-
-In this section, you'll create the Resend Module Provider's service and the methods necessary to send an email with Resend.
-
-Start by creating the file `src/modules/resend/service.ts` with the following content:
-
-```ts title="src/modules/resend/service.ts" highlights={serviceHighlights1}
-import {
- AbstractNotificationProviderService,
-} from "@medusajs/framework/utils"
-import {
- Logger,
-} from "@medusajs/framework/types"
-import {
- Resend,
-} from "resend"
-
-type ResendOptions = {
- api_key: string
- from: string
- html_templates?: Record
-}
-
-class ResendNotificationProviderService extends AbstractNotificationProviderService {
- static identifier = "notification-resend"
- private resendClient: Resend
- private options: ResendOptions
- private logger: Logger
-
- // ...
-}
-
-export default ResendNotificationProviderService
-```
-
-A Notification Module Provider's service must extend the `AbstractNotificationProviderService`. It has a `send` method that you'll implement soon. The service must also have an `identifier` static property, which is a unique identifier that the Medusa application will use to register the provider in the database.
-
-The `ResendNotificationProviderService` class also has the following properties:
-
-- `resendClient` of type `Resend` (from the Resend SDK you installed in the previous step) to send emails through Resend.
-- `options` of type `ResendOptions`. Modules accept options through Medusa's configurations. This ensures that the module is reusable across applications and you don't use sensitive variables like API keys directly in your code. The options that the Resend Module Provider accepts are:
- - `api_key`: The Resend API key.
- - `from`: The email address to send the emails from.
- - `html_templates`: An optional object to replace the default subject and template that the Resend Module uses. This is also useful to support custom emails in different Medusa application setups.
-- `logger` property, which is an instance of Medusa's [Logger](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md), to log messages.
-
-To send requests using the `resendClient`, you need to initialize it in the class's constructor. So, add the following constructor to `ResendNotificationProviderService`:
-
-```ts title="src/modules/resend/service.ts"
-// ...
-
-type InjectedDependencies = {
- logger: Logger
-}
-
-class ResendNotificationProviderService extends AbstractNotificationProviderService {
- // ...
- constructor(
- { logger }: InjectedDependencies,
- options: ResendOptions
- ) {
- super()
- this.resendClient = new Resend(options.api_key)
- this.options = options
- this.logger = logger
- }
-}
-```
-
-A module's service accepts two parameters:
-
-1. Dependencies resolved from the [Module's container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md), which is the module's local registry that the Medusa application adds framework tools to. In this service, you resolve the [Logger utility](https://docs.medusajs.com/docs/learn/debugging-and-testing/logging/index.html.md) from the module's container.
-2. The module's options that are passed to the module in Medusa's configuration as you'll see in a later section.
-
-Using the API key passed in the module's options, you initialize the Resend client. You also set the `options` and `logger` properties.
-
-#### Validate Options Method
-
-A Notification Module Provider's service can implement a static `validateOptions` method that ensures the options passed to the module through Medusa's configurations are valid.
-
-So, add to the `ResendNotificationProviderService` the `validateOptions` method:
-
-```ts title="src/modules/resend/service.ts"
-// other imports...
-import {
- // other imports...
- MedusaError,
-} from "@medusajs/framework/utils"
-
-// ...
-
-class ResendNotificationProviderService extends AbstractNotificationProviderService {
- // ...
- static validateOptions(options: Record) {
- if (!options.api_key) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Option `api_key` is required in the provider's options."
- )
- }
- if (!options.from) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Option `from` is required in the provider's options."
- )
- }
- }
-}
-```
-
-In the `validateOptions` method, you throw an error if the `api_key` or `from` options aren't passed to the module. To throw errors, you use `MedusaError` from the Modules SDK. This ensures errors follow Medusa's conventions and are displayed similar to Medusa's errors.
-
-#### Implement Template Methods
-
-Each email type has a different template and content. For example, order confirmation emails show the order's details, whereas customer confirmation emails show a greeting message to the customer.
-
-So, add two methods to the `ResendNotificationProviderService` class that retrieve the email template and subject of a specified template type:
-
-```ts title="src/modules/resend/service.ts" highlights={serviceHighlights2}
-// imports and types...
-
-enum Templates {
- ORDER_PLACED = "order-placed",
-}
-
-const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
- // TODO add templates
-}
-
-class ResendNotificationProviderService extends AbstractNotificationProviderService {
- // ...
- getTemplate(template: Templates) {
- if (this.options.html_templates?.[template]) {
- return this.options.html_templates[template].content
- }
- const allowedTemplates = Object.keys(templates)
-
- if (!allowedTemplates.includes(template)) {
- return null
- }
-
- return templates[template]
- }
-
- getTemplateSubject(template: Templates) {
- if (this.options.html_templates?.[template]?.subject) {
- return this.options.html_templates[template].subject
- }
- switch(template) {
- case Templates.ORDER_PLACED:
- return "Order Confirmation"
- default:
- return "New Email"
- }
- }
-}
-```
-
-You first define a `Templates` enum, which holds the names of supported template types. You can add more template types to this enum later. You also define a `templates` variable that specifies the React template for each template type. You'll add templates to this variable later.
-
-In the `ResendNotificationProviderService` you add two methods:
-
-- `getTemplate`: Retrieve the template of a template type. If the `html_templates` option is set for the specified template type, you return its `content`'s value. Otherwise, you retrieve the template from the `templates` variable.
-- `getTemplateSubject`: Retrieve the subject of a template type. If a `subject` is passed for the template type in the `html_templates`, you return its value. Otherwise, you return a subject based on the template type.
-
-You'll use these methods in the `send` method next.
-
-#### Implement Send Method
-
-In this section, you'll implement the `send` method of `ResendNotificationProviderService`. When you send a notification through the email channel later using the Notification Module, the Notification Module's service will use this `send` method under the hood to send the email with Resend.
-
-In the `send` method, you'll retrieve the template and subject of the email template, then send the email using the Resend client.
-
-Add the `send` method to the `ResendNotificationProviderService` class:
-
-```ts title="src/modules/resend/service.ts" highlights={serviceHighlights3}
-// other imports...
-import {
- // ...
- ProviderSendNotificationDTO,
- ProviderSendNotificationResultsDTO,
-} from "@medusajs/framework/types"
-import {
- // ...
- CreateEmailOptions,
-} from "resend"
-
-class ResendNotificationProviderService extends AbstractNotificationProviderService {
- // ...
- async send(
- notification: ProviderSendNotificationDTO
- ): Promise {
- const template = this.getTemplate(notification.template as Templates)
-
- if (!template) {
- this.logger.error(`Couldn't find an email template for ${notification.template}. The valid options are ${Object.values(Templates)}`)
- return {}
- }
-
- const emailOptions: CreateEmailOptions = {
- from: this.options.from,
- to: [notification.to],
- subject: this.getTemplateSubject(notification.template as Templates),
- html: "",
- }
-
- if (typeof template === "string") {
- emailOptions.html = template
- } else {
- emailOptions.react = template(notification.data)
- delete emailOptions.html
- }
-
- const { data, error } = await this.resendClient.emails.send(emailOptions)
-
- if (error) {
- this.logger.error(`Failed to send email`, error)
- return {}
- }
-
- return { id: data.id }
- }
-}
-```
-
-The `send` method receives the notification details object as a parameter. Some of its properties include:
-
-- `to`: The address to send the notification to.
-- `template`: The template type of the notification.
-- `data`: The data useful for the email type. For example, when sending an order-confirmation email, `data` would hold the order's details.
-
-In the method, you retrieve the template and subject of the email using the methods you defined earlier. Then, you put together the data to pass to Resend, such as the email address to send the notification to and the email address to send from. Also, if the email's template is a string, it's passed as an HTML template. Otherwise, it's passed as a React template.
-
-Finally, you use the `emails.send` method of the Resend client to send the email. If an error occurs you log it in the terminal. Otherwise, you return the ID of the send email as received from Resend. Medusa uses this ID when creating the notification in its database.
-
-### Export Module Definition
-
-The `ResendNotificationProviderService` class now has the methods necessary to start sending emails.
-
-Next, you must export the module provider's definition, which lets Medusa know what module this provider belongs to and its service.
-
-Create the file `src/modules/resend/index.ts` with the following content:
-
-```ts title="src/modules/resend/index.ts"
-import {
- ModuleProvider,
- Modules,
-} from "@medusajs/framework/utils"
-import ResendNotificationProviderService from "./service"
-
-export default ModuleProvider(Modules.NOTIFICATION, {
- services: [ResendNotificationProviderService],
-})
-```
-
-You export the module provider's definition using `ModuleProvider` from the Modules SDK. It accepts as a first parameter the name of the module that this provider belongs to, which is the Notification Module. It also accepts as a second parameter an object having a `service` property indicating the provider's service.
-
-### Add Module to Configurations
-
-Finally, to register modules and module providers in Medusa, you must add them to Medusa's configurations.
-
-Medusa's configurations are set in the `medusa-config.ts` file, which is at the root directory of your Medusa application. The configuration object accepts a `modules` array, whose value is an array of modules to add to the application.
-
-Add the `modules` property to the exported configurations in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/notification",
- options: {
- providers: [
- {
- resolve: "./src/modules/resend",
- id: "resend",
- options: {
- channels: ["email"],
- api_key: process.env.RESEND_API_KEY,
- from: process.env.RESEND_FROM_EMAIL,
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-In the `modules` array, you pass a module object having the following properties:
-
-- `resolve`: The NPM package of the Notification Module. Since the Resend Module is a Notification Module Provider, it'll be passed in the options of the Notification Module.
-- `options`: An object of options to pass to the module. It has a `providers` property which is an array of module providers to register. Each module provider object has the following properties:
- - `resolve`: The path to the module provider to register in the application. It can also be the name of an NPM package.
- - `id`: A unique ID, which Medusa will use along with the `identifier` static property that you set earlier in the class to identify this module provider.
- - `options`: An object of options to pass to the module provider. These are the options you expect and use in the module provider's service. You must also specify the `channels` option, which indicates the channels that this provider sends notifications through.
-
-Some of the module's options, such as the Resend API key, are set in environment variables. So, add the following environment variables to `.env`:
-
-```shell
-RESEND_FROM_EMAIL=onboarding@resend.dev
-RESEND_API_KEY=
-```
-
-Where:
-
-- `RESEND_FROM_EMAIL`: The email to send emails from. If you've configured the custom domain as explained in [Step 2](#step-2-prepare-resend-account), change this email to an email from your custom domain. Otherwise, you can use `onboarding@resend.dev` for development purposes.
-- `RESEND_API_KEY` is the API key of your Resend account. To retrieve it:
- - Go to API Keys in the sidebar.
- - Click on the Create API Key button.
-
-
-
-- In the form that opens, enter a name for the API key (for example, Medusa). You can keep its permissions to Full Access or change it to Sending Access. Once you're done, click Add.
-
-
-
-- A new pop-up will show with your API key hidden. Copy it before closing the pop-up, since you can't access the key again afterwards. Use its value for the `RESEND_API_KEY` environment variable.
-
-
-
-Your Resend Module Provider is all set up. You'll test it out in a later section.
-
-***
-
-## Step 5: Add Order Confirmation Template
-
-In this step, you'll add a React template for order confirmation emails. You'll create it using the [react-email](https://github.com/resend/react-email) package you installed earlier. You can follow the same steps for other email templates, such as for customer confirmation.
-
-Create the directory `src/modules/resend/emails` that will hold the email templates. Then, to add the template for order confirmation, create the file `src/modules/resend/emails/order-placed.tsx` with the following content:
-
-```tsx title="src/modules/resend/emails/order-placed.tsx" highlights={templateHighlights}
-import { Text, Column, Container, Heading, Html, Img, Row, Section } from "@react-email/components"
-import { BigNumberValue, OrderDTO } from "@medusajs/framework/types"
-
-type OrderPlacedEmailProps = {
- order: OrderDTO
-}
-
-function OrderPlacedEmailComponent({ order }: OrderPlacedEmailProps) {
- const formatter = new Intl.NumberFormat([], {
- style: "currency",
- currencyDisplay: "narrowSymbol",
- currency: order.currency_code,
- })
-
- const formatPrice = (price: BigNumberValue) => {
- if (typeof price === "number") {
- return formatter.format(price)
- }
-
- if (typeof price === "string") {
- return formatter.format(parseFloat(price))
- }
-
- return price?.toString() || ""
- }
-
- return (
-
- Thank you for your order
- {order.email}'s Items
-
- {order.items.map((item) => {
- return (
-
-
-
-
-
-
-
- {item.product_title}
-
- {item.variant_title}
- {formatPrice(item.total)}
-
-
-
- )
- })}
-
-
- )
-}
-
-export const orderPlacedEmail = (props: OrderPlacedEmailProps) => (
-
-)
-```
-
-You define the `OrderPlacedEmailComponent` which is a React email template that shows the order's details, such as items and their totals. The component accepts an `order` object as a prop.
-
-You also export an `orderPlacedEmail` function, which accepts props as an input and returns the `OrderPlacedEmailComponent` passing it the props. Because you can't use JSX syntax in `src/modules/resend/service.ts`, you'll import this function instead.
-
-Next, update the `templates` variable in `src/modules/resend/service.ts` to assign this template to the `order-placed` template type:
-
-```ts title="src/modules/resend/service.ts"
-// other imports...
-import { orderPlacedEmail } from "./emails/order-placed"
-
-const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
- [Templates.ORDER_PLACED]: orderPlacedEmail,
-}
-```
-
-The `ResendNotificationProviderService` will now use the `OrderPlacedEmailComponent` as the template of order confirmation emails.
-
-***
-
-## Step 6: Send Email when Order is Placed
-
-Medusa has an event system that emits an event when a commerce operation is performed. You can then listen and handle that event in an asynchronous function called a subscriber.
-
-So, to send a confirmation email when a customer places an order, which is a commerce operation that Medusa already implements, you don't need to extend or hack your way into Medusa's implementation as you would do with other commerce platforms.
-
-Instead, you'll create a subscriber that listens to the `order.placed` event and sends an email when the event is emitted.
-
-Learn more about Medusa's event system in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md).
-
-### Send Order Confirmation Email Workflow
-
-To send the order confirmation email, you need to retrieve the order's details first, then use the Notification Module's service to send the email. To implement this flow, you'll create a workflow.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow like you construct a function, but it's a special function that allows you to track its executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in a subscriber.
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md)
-
-#### Send Notification Step
-
-You'll start by implementing the step of the workflow that sends the notification. To do that, create the file `src/workflows/steps/send-notification.ts` with the following content:
-
-```ts title="src/workflows/steps/send-notification.ts"
-import { Modules } from "@medusajs/framework/utils"
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-import { CreateNotificationDTO } from "@medusajs/framework/types"
-
-export const sendNotificationStep = createStep(
- "send-notification",
- async (data: CreateNotificationDTO[], { container }) => {
- const notificationModuleService = container.resolve(
- Modules.NOTIFICATION
- )
- const notification = await notificationModuleService.createNotifications(data)
- return new StepResponse(notification)
- }
-)
-```
-
-You define the `sendNotificationStep` using the `createStep` function that accepts two parameters:
-
-- A string indicating the step's unique name.
-- The step's function definition as a second parameter. It accepts the step's input as a first parameter, and an object of options as a second.
-
-The `container` property in the second parameter is an instance of the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of framework and commerce tools, such a module's service, that you can resolve to utilize their functionalities.
-
-The Medusa container is accessible by all customizations, such as workflows and subscribers, except for modules. Each module has its own container with framework tools like the Logger utility.
-
-In the step function, you resolve the Notification Module's service, and use its `createNotifications` method, passing it the notification's data that the step receives as an input.
-
-The step returns an instance of `StepResponse`, which must be returned by any step. It accepts as a parameter the data to return to the workflow that executed this step.
-
-#### Workflow Implementation
-
-You'll now create the workflow that uses the `sendNotificationStep` to send the order confirmation email.
-
-Create the file `src/workflows/send-order-confirmation.ts` with the following content:
-
-```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
-import {
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-import { sendNotificationStep } from "./steps/send-notification"
-
-type WorkflowInput = {
- id: string
-}
-
-export const sendOrderConfirmationWorkflow = createWorkflow(
- "send-order-confirmation",
- ({ id }: WorkflowInput) => {
- // @ts-ignore
- const { data: orders } = useQueryGraphStep({
- entity: "order",
- fields: [
- "id",
- "email",
- "currency_code",
- "total",
- "items.*",
- ],
- filters: {
- id,
- },
- })
-
- const notification = sendNotificationStep([{
- to: orders[0].email,
- channel: "email",
- template: "order-placed",
- data: {
- order: orders[0],
- },
- }])
-
- return new WorkflowResponse(notification)
- }
-)
-```
-
-You create a workflow using `createWorkflow` from the Workflows SDK. It accepts the workflow's unique name as a first parameter.
-
-It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps:
-
-1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details.
-2. `sendNotificationStep` which is the step you implemented. You pass it an array with one object, which is the notification's details having following properties:
- - `to`: The address to send the email to. You pass the customer's email that is stored in the order.
- - `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service.
- - `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here.
- - `data`: The data to pass to the email template, which is the order's details.
-
-A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/constructor-constraints/index.html.md).
-
-You'll execute the workflow when you create the subscriber next.
-
-#### Add the Order Placed Subscriber
-
-Now that you have the workflow to send an order-confirmation email, you'll execute it in a subscriber that's executed whenever an order is placed.
-
-You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. So, create the file `src/subscribers/order-placed.ts` with the following content:
-
-```ts title="src/subscribers/order-placed.ts" highlights={subscriberHighlights}
-import type {
- SubscriberArgs,
- SubscriberConfig,
-} from "@medusajs/framework"
-import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
-
-export default async function orderPlacedHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- await sendOrderConfirmationWorkflow(container)
- .run({
- input: {
- id: data.id,
- },
- })
-}
-
-export const config: SubscriberConfig = {
- event: "order.placed",
-}
-```
-
-A subscriber file exports:
-
-- An asynchronous function that's executed whenever the associated event is emitted, which is the `order.placed` event.
-- A configuration object with an `event` property indicating the event the subscriber is listening to.
-
-The subscriber function accepts the event's details as a first paramter which has a `data` property that holds the data payload of the event. For example, Medusa emits the `order.placed` event with the order's ID in the data payload. The function also accepts as a second parameter the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md).
-
-In the function, you execute the `sendOrderConfirmationWorkflow` by invoking it, passing it the `container`, then using its `run` method. The `run` method accepts an object having an `input` property, which is the input to pass to the workflow. You pass the ID of the placed order as received in the event's data payload.
-
-This subscriber now runs whenever an order is placed. You'll see this in action in the next section.
-
-***
-
-## Test it Out: Place an Order
-
-To test out the Resend integration, you'll place an order using the [Next.js storefront](https://docs.medusajs.com/docs/learn/storefront-development/nextjs-starter/index.html.md) that you installed as part of installing Medusa.
-
-Start your Medusa application first:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, in the Next.js storefront's directory (which was installed in a directory outside of the Medusa application's directory with the name `{project-name}-storefront`, where `{project-name}` is the name of the Medusa application's directory), run the following command to start the storefront:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, open the storefront in your browser at `http://localhost:8000` and:
-
-1. Go to Menu -> Store.
-
-
-
-2\. Click on a product, select its options, and add it to the cart.
-
-
-
-3\. Click on Cart at the top right, then click Go to Cart.
-
-
-
-4\. On the cart's page, click on the "Go to checkout" button.
-
-
-
-5\. On the checkout page, when entering the shipping address, make sure to set the email to your Resend account's email if you didn't set up a custom domain.
-
-
-
-6\. After entering the shipping address, choose a delivery and payment methods, then click the Place Order button.
-
-Once the order is placed, you'll find the following message logged in the Medusa application's terminal:
-
-```bash
-info: Processing order.placed which has 1 subscribers
-```
-
-This indicates that the `order.placed` event was emitted and its subscriber, which you added in the previous step, is executed.
-
-If you check the inbox of the email address you specified in the shipping address, you'll find a new email with the order's details.
-
-
-
-***
-
-## Next Steps
-
-You've now integrated Medusa with Resend. You can add more templates for other emails, such as customer registration confirmation, user invites, and more. Check out the [Events Reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/events-reference/index.html.md) for a list of all events that the Medusa application emits.
-
-If you're new to Medusa, check out the [main documentation](https://docs.medusajs.com/docs/learn/index.html.md), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
-
-To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md).
-
-
# Integrate Medusa with ShipStation (Fulfillment)
In this guide, you'll learn how to integrate Medusa with ShipStation.
@@ -49563,313 +49580,313 @@ To learn more about the commerce features that Medusa provides, check out Medusa
## JS SDK Admin
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.create/index.html.md)
- [batchSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.batchSalesChannels/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.create/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md)
- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md)
-- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
-- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
-- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md)
-- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md)
-- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md)
-- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
-- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md)
-- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
-- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
-- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
-- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md)
-- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
-- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/index.html.md)
-- [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md)
-- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/index.html.md)
-- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md)
-- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancel/index.html.md)
-- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/index.html.md)
-- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
-- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md)
-- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md)
-- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.create/index.html.md)
-- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
-- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md)
-- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md)
-- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md)
-- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
-- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md)
-- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.removeItem/index.html.md)
-- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md)
- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md)
+- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md)
+- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/index.html.md)
+- [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md)
+- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancel/index.html.md)
+- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md)
+- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md)
+- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md)
+- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
+- [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md)
+- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md)
+- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md)
+- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md)
+- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
+- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md)
+- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md)
+- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.removeItem/index.html.md)
+- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md)
+- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
+- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
+- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
+- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md)
+- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md)
+- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md)
+- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md)
+- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
+- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
+- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
+- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
+- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
+- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
+- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md)
-- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
+- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
-- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md)
-- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
-- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
-- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md)
+- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md)
+- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md)
- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md)
-- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md)
+- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md)
- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundShipping/index.html.md)
- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/index.html.md)
-- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md)
-- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md)
-- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md)
-- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md)
+- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md)
- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md)
-- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md)
-- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md)
-- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md)
-- [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md)
-- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
-- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
-- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md)
-- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md)
- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md)
+- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md)
+- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md)
+- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
+- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md)
- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md)
+- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md)
+- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md)
+- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md)
+- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md)
+- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md)
+- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md)
+- [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md)
- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
- [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md)
-- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancel/index.html.md)
- [cancelTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelTransfer/index.html.md)
-- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
+- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md)
- [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md)
-- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md)
+- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md)
+- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md)
- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
-- [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md)
-- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md)
-- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md)
-- [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md)
-- [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md)
- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
+- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
+- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md)
-- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md)
-- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
+- [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md)
+- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md)
+- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
+- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md)
+- [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/index.html.md)
+- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
+- [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
- [batchVariantInventoryItems](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariantInventoryItems/index.html.md)
-- [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md)
- [confirmImport](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.confirmImport/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md)
+- [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md)
- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
-- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md)
- [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md)
+- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md)
-- [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md)
+- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md)
+- [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md)
- [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md)
- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md)
-- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md)
+- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md)
- [updateVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateVariant/index.html.md)
-- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
- [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md)
- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
+- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md)
- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md)
-- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md)
-- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
- [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md)
-- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md)
+- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
+- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md)
+- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md)
- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md)
- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md)
- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md)
- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
-- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
-- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
+- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
+- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
+- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md)
-- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md)
-- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md)
-- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
-- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md)
+- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md)
+- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md)
+- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md)
- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
-- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
-- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
+- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md)
+- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md)
-- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md)
-- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
-- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md)
+- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
+- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md)
+- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.retrieve/index.html.md)
## JS SDK Auth
- [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md)
-- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md)
- [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md)
-- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md)
- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md)
- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md)
+- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md)
+- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md)
- [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md)
@@ -49877,11 +49894,11 @@ To learn more about the commerce features that Medusa provides, check out Medusa
- [cart](https://docs.medusajs.com/references/js-sdk/store/cart/index.html.md)
- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md)
-- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md)
-- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md)
-- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md)
-- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
- [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md)
+- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md)
+- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md)
+- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
+- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md)
- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md)
- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md)
@@ -50551,6 +50568,204 @@ These layout components allow you to set the layout of your [UI routes](https://
These components allow you to use common Medusa Admin components in your custom UI routes and widgets.
+# Single Column Layout - Admin Components
+
+The Medusa Admin has pages with a single column of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/single-column.tsx"
+export type SingleColumnLayoutProps = {
+ children: React.ReactNode
+}
+
+export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+The `SingleColumnLayout` accepts the content in the `children` props.
+
+***
+
+## Example
+
+Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Header } from "../../components/header"
+
+const CustomPage = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
+
+
+# Two Column Layout - Admin Components
+
+The Medusa Admin has pages with two columns of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/two-column.tsx"
+export type TwoColumnLayoutProps = {
+ firstCol: React.ReactNode
+ secondCol: React.ReactNode
+}
+
+export const TwoColumnLayout = ({
+ firstCol,
+ secondCol,
+}: TwoColumnLayoutProps) => {
+ return (
+
+
+ {firstCol}
+
+
+ {secondCol}
+
+
+ )
+}
+```
+
+The `TwoColumnLayout` accepts two props:
+
+- `firstCol` indicating the content of the first column.
+- `secondCol` indicating the content of the second column.
+
+***
+
+## Example
+
+Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { Header } from "../../components/header"
+import { TwoColumnLayout } from "../../layouts/two-column"
+
+const CustomPage = () => {
+ return (
+
+
+
+ }
+ secondCol={
+
+
+
+ }
+ />
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
+
+
+# Container - Admin Components
+
+The Medusa Admin wraps each section of a page in a container.
+
+
+
+To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content:
+
+```tsx
+import {
+ Container as UiContainer,
+ clx,
+} from "@medusajs/ui"
+
+type ContainerProps = React.ComponentProps
+
+export const Container = (props: ContainerProps) => {
+ return (
+
+ )
+}
+```
+
+The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions.
+
+***
+
+## Example
+
+Use that `Container` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+
+const ProductWidget = () => {
+ return (
+
+
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
+
+
# Action Menu - Admin Components
The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon.
@@ -50776,926 +50991,6 @@ export default ProductWidget
```
-# Container - Admin Components
-
-The Medusa Admin wraps each section of a page in a container.
-
-
-
-To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content:
-
-```tsx
-import {
- Container as UiContainer,
- clx,
-} from "@medusajs/ui"
-
-type ContainerProps = React.ComponentProps
-
-export const Container = (props: ContainerProps) => {
- return (
-
- )
-}
-```
-
-The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions.
-
-***
-
-## Example
-
-Use that `Container` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-
-const ProductWidget = () => {
- return (
-
-
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
-
-
-# Data Table - Admin Components
-
-This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0).
-
-The [DataTable component in Medusa UI](https://docs.medusajs.com/ui/components/data-table/index.html.md) allows you to display data in a table with sorting, filtering, and pagination. It's used across the Medusa Admin dashboard to showcase a list of items, such as a list of products.
-
-
-
-You can use this component in your Admin Extensions to display data in a table format, especially if you're retrieving them from API routes of the Medusa application.
-
-This guide focuses on how to use the `DataTable` component while fetching data from the backend. Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages.
-
-## Example: DataTable with Data Fetching
-
-In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting.
-
-Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- createDataTableColumnHelper,
-} from "@medusajs/ui"
-import {
- HttpTypes,
-} from "@medusajs/framework/types"
-
-const columnHelper = createDataTableColumnHelper()
-
-const columns = [
- columnHelper.accessor("title", {
- header: "Title",
- // Enables sorting for the column.
- enableSorting: true,
- // If omitted, the header will be used instead if it's a string,
- // otherwise the accessor key (id) will be used.
- sortLabel: "Title",
- // If omitted the default value will be "A-Z"
- sortAscLabel: "A-Z",
- // If omitted the default value will be "Z-A"
- sortDescLabel: "Z-A",
- }),
- columnHelper.accessor("status", {
- header: "Status",
- cell: ({ getValue }) => {
- const status = getValue()
- return (
-
- {status === "published" ? "Published" : "Draft"}
-
- )
- },
- }),
-]
-```
-
-`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters:
-
-1. The column's key in the table's data.
-2. An object with the following properties:
- - `header`: The column's header.
- - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell.
- - `enableSorting`: (optional) A boolean that enables sorting data by this column.
- - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used.
- - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z".
- - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A".
-
-Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.
-
-To define the filters, add the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import {
- // ...
- createDataTableFilterHelper,
-} from "@medusajs/ui"
-
-const filterHelper = createDataTableFilterHelper()
-
-const filters = [
- filterHelper.accessor("status", {
- type: "select",
- label: "Status",
- options: [
- {
- label: "Published",
- value: "published",
- },
- {
- label: "Draft",
- value: "draft",
- },
- ],
- }),
-]
-```
-
-`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters:
-
-1. The key of a column in the table's data.
-2. An object with the following properties:
- - `type`: The type of filter. It can be either:
- - `select`: A select dropdown allowing users to choose multiple values.
- - `radio`: A radio button allowing users to choose one value.
- - `date`: A date picker allowing users to choose a date.
- - `label`: The filter's label.
- - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by.
-
-You'll now start creating the UI widget's component. Start by adding the necessary state variables:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import {
- // ...
- DataTablePaginationState,
- DataTableFilteringState,
- DataTableSortingState,
-} from "@medusajs/ui"
-import { useMemo, useState } from "react"
-
-// ...
-
-const limit = 15
-
-const CustomPage = () => {
- const [pagination, setPagination] = useState({
- pageSize: limit,
- pageIndex: 0,
- })
- const [search, setSearch] = useState("")
- const [filtering, setFiltering] = useState({})
- const [sorting, setSorting] = useState(null)
-
- const offset = useMemo(() => {
- return pagination.pageIndex * limit
- }, [pagination])
- const statusFilters = useMemo(() => {
- return (filtering.status || []) as ProductStatus
- }, [filtering])
-
- // TODO add data fetching logic
-}
-```
-
-In the component, you've added the following state variables:
-
-- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties:
- - `pageSize`: The number of items to show per page.
- - `pageIndex`: The current page index.
-- `search`: A string that holds the search query.
-- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state.
-- `sorting`: An object of type `DataTableSortingState` that holds the sorting state.
-
-You've also added two memoized variables:
-
-- `offset`: How many items to skip when fetching data based on the current page.
-- `statusFilters`: The selected status filters, if any.
-
-Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { sdk } from "../../lib/config"
-import { useQuery } from "@tanstack/react-query"
-```
-
-This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list({
- limit,
- offset,
- q: search,
- status: statusFilters,
- order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
- }),
- queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
-})
-
-// TODO configure data table
-```
-
-You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method:
-
-- `limit`: The number of products to fetch per page.
-- `offset`: The number of products to skip based on the current page.
-- `q`: The search query, if set.
-- `status`: The status filters, if set.
-- `order`: The sorting order, if set.
-
-So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
-
-Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- useDataTable,
-} from "@medusajs/ui"
-import { useNavigate } from "react-router-dom"
-```
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-const navigate = useNavigate()
-
-const table = useDataTable({
- columns,
- data: data?.products || [],
- getRowId: (row) => row.id,
- rowCount: data?.count || 0,
- isLoading,
- pagination: {
- state: pagination,
- onPaginationChange: setPagination,
- },
- search: {
- state: search,
- onSearchChange: setSearch,
- },
- filtering: {
- state: filtering,
- onFilteringChange: setFiltering,
- },
- filters,
- sorting: {
- // Pass the pagination state and updater to the table instance
- state: sorting,
- onSortingChange: setSorting,
- },
- onRowClick: (event, row) => {
- // Handle row click, for example
- navigate(`/products/${row.id}`)
- },
-})
-
-// TODO render component
-```
-
-The `useDataTable` hook accepts an object with the following properties:
-
-- columns: (\`array\`) The columns to display in the data table. You created this using the \`createDataTableColumnHelper\` utility.
-- data: (\`array\`) The products fetched from the Medusa application.
-- getRowId: (\`function\`) A function that returns the unique ID of a row.
-- rowCount: (\`number\`) The total number of products that can be retrieved. This is used to determine the number of pages.
-- isLoading: (\`boolean\`) A boolean that indicates if the data is being fetched.
-- pagination: (\`object\`) An object to configure pagination.
-
- - state: (\`object\`) The pagination React state variable.
-
- - onPaginationChange: (\`function\`) A function that updates the pagination state.
-- search: (\`object\`) An object to configure searching.
-
- - state: (\`string\`) The search query React state variable.
-
- - onSearchChange: (\`function\`) A function that updates the search query state.
-- filtering: (\`object\`) An object to configure filtering.
-
- - state: (\`object\`) The filtering React state variable.
-
- - onFilteringChange: (\`function\`) A function that updates the filtering state.
-- filters: (\`array\`) The filters to display in the data table. You created this using the \`createDataTableFilterHelper\` utility.
-- sorting: (\`object\`) An object to configure sorting.
-
- - state: (\`object\`) The sorting React state variable.
-
- - onSortingChange: (\`function\`) A function that updates the sorting state.
-- onRowClick: (\`function\`) A function that allows you to perform an action when the user clicks on a row. In this example, you navigate to the product's detail page.
-
- - event: (\`mouseevent\`) An instance of the \[MouseClickEvent]\(https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object.
-
- - row: (\`object\`) The data of the row that was clicked.
-
-Finally, you'll render the data table. But first, add the following imports at the top of the page:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- DataTable,
-} from "@medusajs/ui"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Container } from "../../components/container"
-```
-
-Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard.
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-return (
-
-
-
-
- Products
-
-
-
-
-
-
-
-
-
-
-
-)
-```
-
-You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table.
-
-Lastly, export the component and the UI widget's configuration at the end of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-
-// ...
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities.
-
-### Full Example Code
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import {
- Badge,
- createDataTableColumnHelper,
- createDataTableFilterHelper,
- DataTable,
- DataTableFilteringState,
- DataTablePaginationState,
- DataTableSortingState,
- Heading,
- useDataTable,
-} from "@medusajs/ui"
-import { useQuery } from "@tanstack/react-query"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { sdk } from "../../lib/config"
-import { useMemo, useState } from "react"
-import { Container } from "../../components/container"
-import { HttpTypes, ProductStatus } from "@medusajs/framework/types"
-
-const columnHelper = createDataTableColumnHelper()
-
-const columns = [
- columnHelper.accessor("title", {
- header: "Title",
- // Enables sorting for the column.
- enableSorting: true,
- // If omitted, the header will be used instead if it's a string,
- // otherwise the accessor key (id) will be used.
- sortLabel: "Title",
- // If omitted the default value will be "A-Z"
- sortAscLabel: "A-Z",
- // If omitted the default value will be "Z-A"
- sortDescLabel: "Z-A",
- }),
- columnHelper.accessor("status", {
- header: "Status",
- cell: ({ getValue }) => {
- const status = getValue()
- return (
-
- {status === "published" ? "Published" : "Draft"}
-
- )
- },
- }),
-]
-
-const filterHelper = createDataTableFilterHelper()
-
-const filters = [
- filterHelper.accessor("status", {
- type: "select",
- label: "Status",
- options: [
- {
- label: "Published",
- value: "published",
- },
- {
- label: "Draft",
- value: "draft",
- },
- ],
- }),
-]
-
-const limit = 15
-
-const CustomPage = () => {
- const [pagination, setPagination] = useState({
- pageSize: limit,
- pageIndex: 0,
- })
- const [search, setSearch] = useState("")
- const [filtering, setFiltering] = useState({})
- const [sorting, setSorting] = useState(null)
-
- const offset = useMemo(() => {
- return pagination.pageIndex * limit
- }, [pagination])
- const statusFilters = useMemo(() => {
- return (filtering.status || []) as ProductStatus
- }, [filtering])
-
- const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list({
- limit,
- offset,
- q: search,
- status: statusFilters,
- order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
- }),
- queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
- })
-
- const table = useDataTable({
- columns,
- data: data?.products || [],
- getRowId: (row) => row.id,
- rowCount: data?.count || 0,
- isLoading,
- pagination: {
- state: pagination,
- onPaginationChange: setPagination,
- },
- search: {
- state: search,
- onSearchChange: setSearch,
- },
- filtering: {
- state: filtering,
- onFilteringChange: setFiltering,
- },
- filters,
- sorting: {
- // Pass the pagination state and updater to the table instance
- state: sorting,
- onSortingChange: setSorting,
- },
- })
-
- return (
-
-
-
-
- Products
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-
-# JSON View - Admin Components
-
-Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
-
-
-
-To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
-
-```tsx title="src/admin/components/json-view-section.tsx"
-import {
- ArrowUpRightOnBox,
- Check,
- SquareTwoStack,
- TriangleDownMini,
- XMarkMini,
-} from "@medusajs/icons"
-import {
- Badge,
- Container,
- Drawer,
- Heading,
- IconButton,
- Kbd,
-} from "@medusajs/ui"
-import Primitive from "@uiw/react-json-view"
-import { CSSProperties, MouseEvent, Suspense, useState } from "react"
-
-type JsonViewSectionProps = {
- data: object
- title?: string
-}
-
-export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
- const numberOfKeys = Object.keys(data).length
-
- return (
-
-
- JSON
-
- {numberOfKeys} keys
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {numberOfKeys}
-
-
-
-
-
-
- esc
-
-
-
-
-
-
-
-
-
-
-
}
- >
-
- } />
- (
- null
- )}
- />
- (
- undefined
- )}
- />
- {
- return (
-
- {Object.keys(value as object).length} items
-
- )
- }}
- />
-
-
-
-
- :
-
- {
- return
- }}
- />
-
-
-
-
-
-
-
- )
-}
-
-type CopiedProps = {
- style?: CSSProperties
- value: object | undefined
-}
-
-const Copied = ({ style, value }: CopiedProps) => {
- const [copied, setCopied] = useState(false)
-
- const handler = (e: MouseEvent) => {
- e.stopPropagation()
- setCopied(true)
-
- if (typeof value === "string") {
- navigator.clipboard.writeText(value)
- } else {
- const json = JSON.stringify(value, null, 2)
- navigator.clipboard.writeText(json)
- }
-
- setTimeout(() => {
- setCopied(false)
- }, 2000)
- }
-
- const styl = { whiteSpace: "nowrap", width: "20px" }
-
- if (copied) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
- )
-}
-```
-
-The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
-
-The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
-
-***
-
-## Example
-
-Use the `JsonViewSection` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { JsonViewSection } from "../components/json-view-section"
-
-const ProductWidget = () => {
- return
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
-
-
-# Header - Admin Components
-
-Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action.
-
-
-
-To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content:
-
-```tsx title="src/admin/components/header.tsx"
-import { Heading, Button, Text } from "@medusajs/ui"
-import React from "react"
-import { Link, LinkProps } from "react-router-dom"
-import { ActionMenu, ActionMenuProps } from "./action-menu"
-
-export type HeadingProps = {
- title: string
- subtitle?: string
- actions?: (
- {
- type: "button",
- props: React.ComponentProps
- link?: LinkProps
- } |
- {
- type: "action-menu"
- props: ActionMenuProps
- } |
- {
- type: "custom"
- children: React.ReactNode
- }
- )[]
-}
-
-export const Header = ({
- title,
- subtitle,
- actions = [],
-}: HeadingProps) => {
- return (
-
- )
-}
-```
-
-The `Header` component shows a title, and optionally a subtitle and action buttons.
-
-The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component.
-
-It accepts the following props:
-
-- title: (\`string\`) The section's title.
-- subtitle: (\`string\`) The section's subtitle.
-- actions: (\`object\[]\`) An array of actions to show.
-
- - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add.
-
- \- If its value is \`button\`, it'll show a button that can have a link or an on-click action.
-
- \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions.
-
- \- If its value is \`custom\`, you can pass any React nodes to render.
-
- - props: (object)
-
- - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions.
-
-***
-
-## Example
-
-Use the `Header` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-
-const ProductWidget = () => {
- return (
-
- {
- alert("You clicked the button.")
- },
- },
- },
- ]}
- />
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component.
-
-
# Forms - Admin Components
The Medusa Admin has two types of forms:
@@ -52269,6 +51564,152 @@ This component uses the [Container](https://docs.medusajs.com/Users/shahednasser
It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form.
+# Header - Admin Components
+
+Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action.
+
+
+
+To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content:
+
+```tsx title="src/admin/components/header.tsx"
+import { Heading, Button, Text } from "@medusajs/ui"
+import React from "react"
+import { Link, LinkProps } from "react-router-dom"
+import { ActionMenu, ActionMenuProps } from "./action-menu"
+
+export type HeadingProps = {
+ title: string
+ subtitle?: string
+ actions?: (
+ {
+ type: "button",
+ props: React.ComponentProps
+ link?: LinkProps
+ } |
+ {
+ type: "action-menu"
+ props: ActionMenuProps
+ } |
+ {
+ type: "custom"
+ children: React.ReactNode
+ }
+ )[]
+}
+
+export const Header = ({
+ title,
+ subtitle,
+ actions = [],
+}: HeadingProps) => {
+ return (
+
+ )
+}
+```
+
+The `Header` component shows a title, and optionally a subtitle and action buttons.
+
+The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component.
+
+It accepts the following props:
+
+- title: (\`string\`) The section's title.
+- subtitle: (\`string\`) The section's subtitle.
+- actions: (\`object\[]\`) An array of actions to show.
+
+ - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add.
+
+ \- If its value is \`button\`, it'll show a button that can have a link or an on-click action.
+
+ \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions.
+
+ \- If its value is \`custom\`, you can pass any React nodes to render.
+
+ - props: (object)
+
+ - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions.
+
+***
+
+## Example
+
+Use the `Header` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+
+const ProductWidget = () => {
+ return (
+
+ {
+ alert("You clicked the button.")
+ },
+ },
+ },
+ ]}
+ />
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component.
+
+
# Section Row - Admin Components
The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
@@ -52361,6 +51802,721 @@ export default ProductWidget
This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
+# JSON View - Admin Components
+
+Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
+
+
+
+To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
+
+```tsx title="src/admin/components/json-view-section.tsx"
+import {
+ ArrowUpRightOnBox,
+ Check,
+ SquareTwoStack,
+ TriangleDownMini,
+ XMarkMini,
+} from "@medusajs/icons"
+import {
+ Badge,
+ Container,
+ Drawer,
+ Heading,
+ IconButton,
+ Kbd,
+} from "@medusajs/ui"
+import Primitive from "@uiw/react-json-view"
+import { CSSProperties, MouseEvent, Suspense, useState } from "react"
+
+type JsonViewSectionProps = {
+ data: object
+ title?: string
+}
+
+export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
+ const numberOfKeys = Object.keys(data).length
+
+ return (
+
+
+ JSON
+
+ {numberOfKeys} keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {numberOfKeys}
+
+
+
+
+
+
+ esc
+
+
+
+
+
+
+
+
+
+
+
}
+ >
+
+ } />
+ (
+ null
+ )}
+ />
+ (
+ undefined
+ )}
+ />
+ {
+ return (
+
+ {Object.keys(value as object).length} items
+
+ )
+ }}
+ />
+
+
+
+
+ :
+
+ {
+ return
+ }}
+ />
+
+
+
+
+
+
+
+ )
+}
+
+type CopiedProps = {
+ style?: CSSProperties
+ value: object | undefined
+}
+
+const Copied = ({ style, value }: CopiedProps) => {
+ const [copied, setCopied] = useState(false)
+
+ const handler = (e: MouseEvent) => {
+ e.stopPropagation()
+ setCopied(true)
+
+ if (typeof value === "string") {
+ navigator.clipboard.writeText(value)
+ } else {
+ const json = JSON.stringify(value, null, 2)
+ navigator.clipboard.writeText(json)
+ }
+
+ setTimeout(() => {
+ setCopied(false)
+ }, 2000)
+ }
+
+ const styl = { whiteSpace: "nowrap", width: "20px" }
+
+ if (copied) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
+```
+
+The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
+
+The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
+
+***
+
+## Example
+
+Use the `JsonViewSection` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { JsonViewSection } from "../components/json-view-section"
+
+const ProductWidget = () => {
+ return
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
+
+
+# Data Table - Admin Components
+
+This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0).
+
+The [DataTable component in Medusa UI](https://docs.medusajs.com/ui/components/data-table/index.html.md) allows you to display data in a table with sorting, filtering, and pagination. It's used across the Medusa Admin dashboard to showcase a list of items, such as a list of products.
+
+
+
+You can use this component in your Admin Extensions to display data in a table format, especially if you're retrieving them from API routes of the Medusa application.
+
+This guide focuses on how to use the `DataTable` component while fetching data from the backend. Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages.
+
+## Example: DataTable with Data Fetching
+
+In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting.
+
+Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ createDataTableColumnHelper,
+} from "@medusajs/ui"
+import {
+ HttpTypes,
+} from "@medusajs/framework/types"
+
+const columnHelper = createDataTableColumnHelper()
+
+const columns = [
+ columnHelper.accessor("title", {
+ header: "Title",
+ // Enables sorting for the column.
+ enableSorting: true,
+ // If omitted, the header will be used instead if it's a string,
+ // otherwise the accessor key (id) will be used.
+ sortLabel: "Title",
+ // If omitted the default value will be "A-Z"
+ sortAscLabel: "A-Z",
+ // If omitted the default value will be "Z-A"
+ sortDescLabel: "Z-A",
+ }),
+ columnHelper.accessor("status", {
+ header: "Status",
+ cell: ({ getValue }) => {
+ const status = getValue()
+ return (
+
+ {status === "published" ? "Published" : "Draft"}
+
+ )
+ },
+ }),
+]
+```
+
+`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters:
+
+1. The column's key in the table's data.
+2. An object with the following properties:
+ - `header`: The column's header.
+ - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell.
+ - `enableSorting`: (optional) A boolean that enables sorting data by this column.
+ - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used.
+ - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z".
+ - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A".
+
+Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.
+
+To define the filters, add the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ createDataTableFilterHelper,
+} from "@medusajs/ui"
+
+const filterHelper = createDataTableFilterHelper()
+
+const filters = [
+ filterHelper.accessor("status", {
+ type: "select",
+ label: "Status",
+ options: [
+ {
+ label: "Published",
+ value: "published",
+ },
+ {
+ label: "Draft",
+ value: "draft",
+ },
+ ],
+ }),
+]
+```
+
+`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters:
+
+1. The key of a column in the table's data.
+2. An object with the following properties:
+ - `type`: The type of filter. It can be either:
+ - `select`: A select dropdown allowing users to choose multiple values.
+ - `radio`: A radio button allowing users to choose one value.
+ - `date`: A date picker allowing users to choose a date.
+ - `label`: The filter's label.
+ - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by.
+
+You'll now start creating the UI widget's component. Start by adding the necessary state variables:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ DataTablePaginationState,
+ DataTableFilteringState,
+ DataTableSortingState,
+} from "@medusajs/ui"
+import { useMemo, useState } from "react"
+
+// ...
+
+const limit = 15
+
+const CustomPage = () => {
+ const [pagination, setPagination] = useState({
+ pageSize: limit,
+ pageIndex: 0,
+ })
+ const [search, setSearch] = useState("")
+ const [filtering, setFiltering] = useState({})
+ const [sorting, setSorting] = useState(null)
+
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
+
+ // TODO add data fetching logic
+}
+```
+
+In the component, you've added the following state variables:
+
+- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties:
+ - `pageSize`: The number of items to show per page.
+ - `pageIndex`: The current page index.
+- `search`: A string that holds the search query.
+- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state.
+- `sorting`: An object of type `DataTableSortingState` that holds the sorting state.
+
+You've also added two memoized variables:
+
+- `offset`: How many items to skip when fetching data based on the current page.
+- `statusFilters`: The selected status filters, if any.
+
+Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { sdk } from "../../lib/config"
+import { useQuery } from "@tanstack/react-query"
+```
+
+This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list({
+ limit,
+ offset,
+ q: search,
+ status: statusFilters,
+ order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
+ }),
+ queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
+})
+
+// TODO configure data table
+```
+
+You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method:
+
+- `limit`: The number of products to fetch per page.
+- `offset`: The number of products to skip based on the current page.
+- `q`: The search query, if set.
+- `status`: The status filters, if set.
+- `order`: The sorting order, if set.
+
+So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
+
+Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ useDataTable,
+} from "@medusajs/ui"
+import { useNavigate } from "react-router-dom"
+```
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+const navigate = useNavigate()
+
+const table = useDataTable({
+ columns,
+ data: data?.products || [],
+ getRowId: (row) => row.id,
+ rowCount: data?.count || 0,
+ isLoading,
+ pagination: {
+ state: pagination,
+ onPaginationChange: setPagination,
+ },
+ search: {
+ state: search,
+ onSearchChange: setSearch,
+ },
+ filtering: {
+ state: filtering,
+ onFilteringChange: setFiltering,
+ },
+ filters,
+ sorting: {
+ // Pass the pagination state and updater to the table instance
+ state: sorting,
+ onSortingChange: setSorting,
+ },
+ onRowClick: (event, row) => {
+ // Handle row click, for example
+ navigate(`/products/${row.id}`)
+ },
+})
+
+// TODO render component
+```
+
+The `useDataTable` hook accepts an object with the following properties:
+
+- columns: (\`array\`) The columns to display in the data table. You created this using the \`createDataTableColumnHelper\` utility.
+- data: (\`array\`) The products fetched from the Medusa application.
+- getRowId: (\`function\`) A function that returns the unique ID of a row.
+- rowCount: (\`number\`) The total number of products that can be retrieved. This is used to determine the number of pages.
+- isLoading: (\`boolean\`) A boolean that indicates if the data is being fetched.
+- pagination: (\`object\`) An object to configure pagination.
+
+ - state: (\`object\`) The pagination React state variable.
+
+ - onPaginationChange: (\`function\`) A function that updates the pagination state.
+- search: (\`object\`) An object to configure searching.
+
+ - state: (\`string\`) The search query React state variable.
+
+ - onSearchChange: (\`function\`) A function that updates the search query state.
+- filtering: (\`object\`) An object to configure filtering.
+
+ - state: (\`object\`) The filtering React state variable.
+
+ - onFilteringChange: (\`function\`) A function that updates the filtering state.
+- filters: (\`array\`) The filters to display in the data table. You created this using the \`createDataTableFilterHelper\` utility.
+- sorting: (\`object\`) An object to configure sorting.
+
+ - state: (\`object\`) The sorting React state variable.
+
+ - onSortingChange: (\`function\`) A function that updates the sorting state.
+- onRowClick: (\`function\`) A function that allows you to perform an action when the user clicks on a row. In this example, you navigate to the product's detail page.
+
+ - event: (\`mouseevent\`) An instance of the \[MouseClickEvent]\(https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object.
+
+ - row: (\`object\`) The data of the row that was clicked.
+
+Finally, you'll render the data table. But first, add the following imports at the top of the page:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ DataTable,
+} from "@medusajs/ui"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Container } from "../../components/container"
+```
+
+Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard.
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+return (
+
+
+
+
+ Products
+
+
+
+
+
+
+
+
+
+
+
+)
+```
+
+You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table.
+
+Lastly, export the component and the UI widget's configuration at the end of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+
+// ...
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities.
+
+### Full Example Code
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import {
+ Badge,
+ createDataTableColumnHelper,
+ createDataTableFilterHelper,
+ DataTable,
+ DataTableFilteringState,
+ DataTablePaginationState,
+ DataTableSortingState,
+ Heading,
+ useDataTable,
+} from "@medusajs/ui"
+import { useQuery } from "@tanstack/react-query"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { sdk } from "../../lib/config"
+import { useMemo, useState } from "react"
+import { Container } from "../../components/container"
+import { HttpTypes, ProductStatus } from "@medusajs/framework/types"
+
+const columnHelper = createDataTableColumnHelper()
+
+const columns = [
+ columnHelper.accessor("title", {
+ header: "Title",
+ // Enables sorting for the column.
+ enableSorting: true,
+ // If omitted, the header will be used instead if it's a string,
+ // otherwise the accessor key (id) will be used.
+ sortLabel: "Title",
+ // If omitted the default value will be "A-Z"
+ sortAscLabel: "A-Z",
+ // If omitted the default value will be "Z-A"
+ sortDescLabel: "Z-A",
+ }),
+ columnHelper.accessor("status", {
+ header: "Status",
+ cell: ({ getValue }) => {
+ const status = getValue()
+ return (
+
+ {status === "published" ? "Published" : "Draft"}
+
+ )
+ },
+ }),
+]
+
+const filterHelper = createDataTableFilterHelper()
+
+const filters = [
+ filterHelper.accessor("status", {
+ type: "select",
+ label: "Status",
+ options: [
+ {
+ label: "Published",
+ value: "published",
+ },
+ {
+ label: "Draft",
+ value: "draft",
+ },
+ ],
+ }),
+]
+
+const limit = 15
+
+const CustomPage = () => {
+ const [pagination, setPagination] = useState({
+ pageSize: limit,
+ pageIndex: 0,
+ })
+ const [search, setSearch] = useState("")
+ const [filtering, setFiltering] = useState({})
+ const [sorting, setSorting] = useState(null)
+
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
+
+ const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list({
+ limit,
+ offset,
+ q: search,
+ status: statusFilters,
+ order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
+ }),
+ queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
+ })
+
+ const table = useDataTable({
+ columns,
+ data: data?.products || [],
+ getRowId: (row) => row.id,
+ rowCount: data?.count || 0,
+ isLoading,
+ pagination: {
+ state: pagination,
+ onPaginationChange: setPagination,
+ },
+ search: {
+ state: search,
+ onSearchChange: setSearch,
+ },
+ filtering: {
+ state: filtering,
+ onFilteringChange: setFiltering,
+ },
+ filters,
+ sorting: {
+ // Pass the pagination state and updater to the table instance
+ state: sorting,
+ onSortingChange: setSorting,
+ },
+ })
+
+ return (
+
+
+
+
+ Products
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+
# Table - Admin Components
If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/data-table/index.html.md) component instead as it provides features for sorting, filtering, pagination, and more with a simpler API.
@@ -52651,145 +52807,6 @@ If `data` isn't `undefined`, you display the `Table` component passing it the fo
To test it out, log into the Medusa Admin and open `http://localhost:9000/app/custom`. You'll find a table of products with pagination.
-# Single Column Layout - Admin Components
-
-The Medusa Admin has pages with a single column of content.
-
-This doesn't include the sidebar, only the main content.
-
-
-
-To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
-
-```tsx title="src/admin/layouts/single-column.tsx"
-export type SingleColumnLayoutProps = {
- children: React.ReactNode
-}
-
-export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
- return (
-
- {children}
-
- )
-}
-```
-
-The `SingleColumnLayout` accepts the content in the `children` props.
-
-***
-
-## Example
-
-Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Header } from "../../components/header"
-
-const CustomPage = () => {
- return (
-
-
-
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
-
-
-# Two Column Layout - Admin Components
-
-The Medusa Admin has pages with two columns of content.
-
-This doesn't include the sidebar, only the main content.
-
-
-
-To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
-
-```tsx title="src/admin/layouts/two-column.tsx"
-export type TwoColumnLayoutProps = {
- firstCol: React.ReactNode
- secondCol: React.ReactNode
-}
-
-export const TwoColumnLayout = ({
- firstCol,
- secondCol,
-}: TwoColumnLayoutProps) => {
- return (
-
-
- {firstCol}
-
-
- {secondCol}
-
-
- )
-}
-```
-
-The `TwoColumnLayout` accepts two props:
-
-- `firstCol` indicating the content of the first column.
-- `secondCol` indicating the content of the second column.
-
-***
-
-## Example
-
-Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { Header } from "../../components/header"
-import { TwoColumnLayout } from "../../layouts/two-column"
-
-const CustomPage = () => {
- return (
-
-
-
- }
- secondCol={
-
-
-
- }
- />
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
-
-
# Service Factory Reference
This section of the documentation provides a reference of the methods generated for services extending the service factory (`MedusaService`), and how to use them.
@@ -52855,142 +52872,6 @@ const posts = await postModuleService.createPosts([
If an array is passed of the method, an array of the created records is also returned.
-# listAndCount Method - Service Factory Reference
-
-This method retrieves a list of records with the total count.
-
-## Retrieve List of Records
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts()
-```
-
-If no parameters are passed, the method returns an array with two items:
-
-1. The first is an array of the first `15` records retrieved.
-2. The second is the total count of records.
-
-***
-
-## Filter Records
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts({
- id: ["123", "321"],
-})
-```
-
-### Parameters
-
-To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an array with two items:
-
-1. The first is an array of the first `15` records retrieved matching the specified filters.
-2. The second is the total count of records matching the specified filters.
-
-***
-
-## Retrieve Relations
-
-This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts({}, {
- relations: ["author"],
-})
-```
-
-### Parameters
-
-To retrieve records with their relations, pass as a second parameter an object having a `relations` property. Its value is an array of relation names.
-
-### Returns
-
-The method returns an array with two items:
-
-1. The first is an array of the first `15` records retrieved.
-2. The second is the total count of records.
-
-***
-
-## Select Properties
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts({}, {
- select: ["id", "name"],
-})
-```
-
-### Parameters
-
-By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
-
-`select`'s value is an array of property names to retrieve.
-
-### Returns
-
-The method returns an array with two items:
-
-1. The first is an array of the first `15` records retrieved.
-2. The second is the total count of records.
-
-***
-
-## Paginate Relations
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts({}, {
- take: 20,
- skip: 10,
-})
-```
-
-### Parameters
-
-To paginate the returned records, the second object parameter accepts the following properties:
-
-- `take`: a number indicating how many records to retrieve. By default, it's `15`.
-- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
-
-### Returns
-
-The method returns an array with two items:
-
-1. The first is an array of the records retrieved. The number of records is less than or equal to `take`'s value.
-2. The second is the total count of records.
-
-***
-
-## Sort Records
-
-```ts
-const [posts, count] = await postModuleService.listAndCountPosts({}, {
- order: {
- name: "ASC",
- },
-})
-```
-
-### Parameters
-
-To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
-
-- `ASC` to sort by this property in the ascending order.
-- `DESC` to sort by this property in the descending order.
-
-### Returns
-
-The method returns an array with two items:
-
-1. The first is an array of the first `15` records retrieved.
-2. The second is the total count of records.
-
-
# delete Method - Service Factory Reference
This method deletes one or more records.
@@ -53031,211 +52912,6 @@ To delete records matching a set of filters, pass an object of filters as a para
Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-# list Method - Service Factory Reference
-
-This method retrieves a list of records.
-
-## Retrieve List of Records
-
-```ts
-const posts = await postModuleService.listPosts()
-```
-
-If no parameters are passed, the method returns an array of the first `15` records.
-
-***
-
-## Filter Records
-
-```ts
-const posts = await postModuleService.listPosts({
- id: ["123", "321"],
-})
-```
-
-### Parameters
-
-To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Retrieve Relations
-
-This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- relations: ["author"],
-})
-```
-
-### Parameters
-
-To retrieve records with their relations, pass as a second parameter an object having a `relations` property. `relations`'s value is an array of relation names.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Select Properties
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- select: ["id", "name"],
-})
-```
-
-### Parameters
-
-By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
-
-`select`'s value is an array of property names to retrieve.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-***
-
-## Paginate Relations
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- take: 20,
- skip: 10,
-})
-```
-
-### Parameters
-
-To paginate the returned records, the second object parameter accepts the following properties:
-
-- `take`: a number indicating how many records to retrieve. By default, it's `15`.
-- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
-
-### Returns
-
-The method returns an array of records. The number of records is less than or equal to `take`'s value.
-
-***
-
-## Sort Records
-
-```ts
-const posts = await postModuleService.listPosts({}, {
- order: {
- name: "ASC",
- },
-})
-```
-
-### Parameters
-
-To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
-
-- `ASC` to sort by this property in the ascending order.
-- `DESC` to sort by this property in the descending order.
-
-### Returns
-
-The method returns an array of the first `15` records matching the filters.
-
-
-# softDelete Method - Service Factory Reference
-
-This method soft deletes one or more records of the data model.
-
-## Soft Delete One Record
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts(
- "123"
-)
-```
-
-### Parameters
-
-To soft delete a record, pass its ID as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: ["123"],
-}
-```
-
-***
-
-## Soft Delete Multiple Records
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts([
- "123",
- "321",
-])
-```
-
-### Parameters
-
-To soft delete multiple records, pass an array of IDs as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: [
- "123",
- "321",
- ],
-}
-```
-
-***
-
-## Soft Delete Records Matching Filters
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts({
- name: "My Post",
-})
-```
-
-### Parameters
-
-To soft delete records matching a set of filters, pass an object of filters as a parameter.
-
-Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: ["123"],
-}
-```
-
-
# restore Method - Service Factory Reference
This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md).
@@ -53380,6 +53056,93 @@ By default, all of the record's properties are retrieved. To select specific one
The method returns the record as an object.
+# softDelete Method - Service Factory Reference
+
+This method soft deletes one or more records of the data model.
+
+## Soft Delete One Record
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts(
+ "123"
+)
+```
+
+### Parameters
+
+To soft delete a record, pass its ID as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+***
+
+## Soft Delete Multiple Records
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts([
+ "123",
+ "321",
+])
+```
+
+### Parameters
+
+To soft delete multiple records, pass an array of IDs as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: [
+ "123",
+ "321",
+ ],
+}
+```
+
+***
+
+## Soft Delete Records Matching Filters
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts({
+ name: "My Post",
+})
+```
+
+### Parameters
+
+To soft delete records matching a set of filters, pass an object of filters as a parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+
# update Method - Service Factory Reference
This method updates one or more records of the data model.
@@ -53503,6 +53266,124 @@ Learn more about accepted filters in [this documentation](https://docs.medusajs.
The method returns an array of objects of updated records.
+# list Method - Service Factory Reference
+
+This method retrieves a list of records.
+
+## Retrieve List of Records
+
+```ts
+const posts = await postModuleService.listPosts()
+```
+
+If no parameters are passed, the method returns an array of the first `15` records.
+
+***
+
+## Filter Records
+
+```ts
+const posts = await postModuleService.listPosts({
+ id: ["123", "321"],
+})
+```
+
+### Parameters
+
+To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Retrieve Relations
+
+This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ relations: ["author"],
+})
+```
+
+### Parameters
+
+To retrieve records with their relations, pass as a second parameter an object having a `relations` property. `relations`'s value is an array of relation names.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Select Properties
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ select: ["id", "name"],
+})
+```
+
+### Parameters
+
+By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
+
+`select`'s value is an array of property names to retrieve.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+***
+
+## Paginate Relations
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ take: 20,
+ skip: 10,
+})
+```
+
+### Parameters
+
+To paginate the returned records, the second object parameter accepts the following properties:
+
+- `take`: a number indicating how many records to retrieve. By default, it's `15`.
+- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
+
+### Returns
+
+The method returns an array of records. The number of records is less than or equal to `take`'s value.
+
+***
+
+## Sort Records
+
+```ts
+const posts = await postModuleService.listPosts({}, {
+ order: {
+ name: "ASC",
+ },
+})
+```
+
+### Parameters
+
+To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
+
+- `ASC` to sort by this property in the ascending order.
+- `DESC` to sort by this property in the descending order.
+
+### Returns
+
+The method returns an array of the first `15` records matching the filters.
+
+
# Filter Records - Service Factory Reference
Many of the service factory's generated methods allow passing filters to perform an operation, such as to update or delete records matching the filters.
@@ -53761,6 +53642,142 @@ In the example above, posts are retrieved if they meet either of the following c
By combining `and` and `or` conditions, you can create complex filters to retrieve records that meet specific criteria.
+# listAndCount Method - Service Factory Reference
+
+This method retrieves a list of records with the total count.
+
+## Retrieve List of Records
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts()
+```
+
+If no parameters are passed, the method returns an array with two items:
+
+1. The first is an array of the first `15` records retrieved.
+2. The second is the total count of records.
+
+***
+
+## Filter Records
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts({
+ id: ["123", "321"],
+})
+```
+
+### Parameters
+
+To retrieve records matching a set of filters, pass an object of the filters as a first parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an array with two items:
+
+1. The first is an array of the first `15` records retrieved matching the specified filters.
+2. The second is the total count of records matching the specified filters.
+
+***
+
+## Retrieve Relations
+
+This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
+ relations: ["author"],
+})
+```
+
+### Parameters
+
+To retrieve records with their relations, pass as a second parameter an object having a `relations` property. Its value is an array of relation names.
+
+### Returns
+
+The method returns an array with two items:
+
+1. The first is an array of the first `15` records retrieved.
+2. The second is the total count of records.
+
+***
+
+## Select Properties
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
+ select: ["id", "name"],
+})
+```
+
+### Parameters
+
+By default, retrieved records have all their properties. To select specific properties to retrieve, pass in the second object parameter a `select` property.
+
+`select`'s value is an array of property names to retrieve.
+
+### Returns
+
+The method returns an array with two items:
+
+1. The first is an array of the first `15` records retrieved.
+2. The second is the total count of records.
+
+***
+
+## Paginate Relations
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
+ take: 20,
+ skip: 10,
+})
+```
+
+### Parameters
+
+To paginate the returned records, the second object parameter accepts the following properties:
+
+- `take`: a number indicating how many records to retrieve. By default, it's `15`.
+- `skip`: a number indicating how many records to skip before the retrieved records. By default, it's `0`.
+
+### Returns
+
+The method returns an array with two items:
+
+1. The first is an array of the records retrieved. The number of records is less than or equal to `take`'s value.
+2. The second is the total count of records.
+
+***
+
+## Sort Records
+
+```ts
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
+ order: {
+ name: "ASC",
+ },
+})
+```
+
+### Parameters
+
+To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be:
+
+- `ASC` to sort by this property in the ascending order.
+- `DESC` to sort by this property in the descending order.
+
+### Returns
+
+The method returns an array with two items:
+
+1. The first is an array of the first `15` records retrieved.
+2. The second is the total count of records.
+
+
Just Getting Started?
diff --git a/www/apps/resources/app/commerce-modules/stock-location/admin-widget-zones/_content.mdx b/www/apps/resources/app/commerce-modules/stock-location/admin-widget-zones/_content.mdx
index f4e8976e5b..b035c6042b 100644
--- a/www/apps/resources/app/commerce-modules/stock-location/admin-widget-zones/_content.mdx
+++ b/www/apps/resources/app/commerce-modules/stock-location/admin-widget-zones/_content.mdx
@@ -43,6 +43,40 @@ import { Table } from "docs-ui"
+
+
+
+ `location.list.side.before`
+
+
+
+
+ Added at the top of the second column in the locations list page. This injection zone is available since [Medusa v2.7.0](https://github.com/medusajs/medusa/releases/tag/v2.7.0).
+
+
+
+
+ \-
+
+
+
+
+
+
+ `location.list.side.after`
+
+
+
+
+ Added at the bottom of the second column in the locations list page. This injection zone is available since [Medusa v2.7.0](https://github.com/medusajs/medusa/releases/tag/v2.7.0).
+
+
+
+
+ \-
+
+
+
diff --git a/www/apps/resources/app/events-reference/page.mdx b/www/apps/resources/app/events-reference/page.mdx
index e29b6b8fbc..82e0d67811 100644
--- a/www/apps/resources/app/events-reference/page.mdx
+++ b/www/apps/resources/app/events-reference/page.mdx
@@ -5,7 +5,7 @@ import CustomerEvents from "../commerce-modules/customer/events/_content.mdx"
import FulfillmentEvents from "../commerce-modules/fulfillment/events/_content.mdx"
import InviteEvents from "../commerce-modules/user/events/_content/invite.mdx"
import OrderEvents from "../commerce-modules/order/events/_content.mdx"
-import PaymentEvents from "../commerce-modules/payment/events/page.mdx"
+import PaymentEvents from "../commerce-modules/payment/events/_content.mdx"
import ProductEvents from "../commerce-modules/product/events/_content/product.mdx"
import ProductCategoryEvents from "../commerce-modules/product/events/_content/product-category.mdx"
import ProductCollectionEvents from "../commerce-modules/product/events/_content/product-collection.mdx"
diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
index 1062ed1dc9..5dfd295a80 100644
--- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
+++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
@@ -1087,6 +1087,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"path": "https://docs.medusajs.com/resources/examples/guides/custom-item-price",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Implement Loyalty Points",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
@@ -2404,6 +2412,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"path": "/commerce-modules/customer/extend",
"title": "Extend Module",
"children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Loyalty Points",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points",
+ "children": []
}
]
},
@@ -5953,6 +5969,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"title": "Implement Quote Management",
"path": "https://docs.medusajs.com/resources/examples/guides/quote-management",
"children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Loyalty Points",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points",
+ "children": []
}
]
},
@@ -12759,6 +12783,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"path": "/commerce-modules/promotion/extend",
"title": "Extend Module",
"children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Loyalty Points",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points",
+ "children": []
}
]
},
diff --git a/www/apps/user-guide/app/customers/manage/page.mdx b/www/apps/user-guide/app/customers/manage/page.mdx
index 45b300e03d..93fdef76c4 100644
--- a/www/apps/user-guide/app/customers/manage/page.mdx
+++ b/www/apps/user-guide/app/customers/manage/page.mdx
@@ -170,6 +170,45 @@ To edit the customer's metadata:
---
+## Manage Customer's Addresses
+
+You can view and manage a customer's address from the "Addresses" section on the customer's details page. This is useful when you need to make changes to the customer's shipping or billing address in an order.
+
+
+
+The Address section and management features are only available after This injection zone is available since [Medusa v2.7.0](https://github.com/medusajs/medusa/releases/tag/v2.7.0).
+
+
+
+### Add Customer Address
+
+To add an address to a customer:
+
+1. Go to the customer's details page.
+2. Click on the "Add" button at the top right of the "Addresses" section.
+3. In the form that opens, enter the address details including name, address line, apartment, postal code, city, country, state, company, and phone number.
+4. Once you're done, click the Save button.
+
+
+
+### Delete Customer Address
+
+
+
+Deleting an address is irreversible.
+
+
+
+To delete a customer address:
+
+1. Go to the customer's details page.
+2. Find the address in the "Addresses" section.
+3. Click on the icon at the right of the address.
+4. Choose Delete from the dropdown.
+5. In the pop-up, enter the address name and click the "Delete" button.
+
+---
+
## Delete a Customer
diff --git a/www/apps/user-guide/app/tips/languages/page.mdx b/www/apps/user-guide/app/tips/languages/page.mdx
index f80e4d1741..7c14d77853 100644
--- a/www/apps/user-guide/app/tips/languages/page.mdx
+++ b/www/apps/user-guide/app/tips/languages/page.mdx
@@ -10,13 +10,13 @@ export const metadata = {
Medusa Admin comes with multi-language support. Multi-language support is only for the Medusa Admin interface, not for the content, such as product description.
-This document covers the complete list of available languages, both official and by the community. To learn how to change the language, refer to [this documentation](../../settings/profile/page.mdx).
+This document covers the complete list of available languages, both official and by the community.
-
+## How to Change the Language
-Can't find your language? Learn how you can contribute by translating the admin to other languages [here](!docs!/learn/resources/contribution-guidelines/admin-translations).
+To learn how to change the language, refer to the [Manage Profile](../../settings/profile/page.mdx) guide.
-
+---
## Official Languages
@@ -30,22 +30,32 @@ Languages listed in this section are contributed by the Medusa community. So, th
-- Español
-- Ελληνικά
+- Čeština
- Deutsch
+- Español
- Français
- Italiano
-- 日本語
+- Lietuviškai
- Polski
- Português (Brasil)
-- Türkçe
-- ไทย
-- Українська
- Română
+- Tiếng Việt
+- Türkçe
+- Ελληνικά
+- Български
- Македонски
- Монгол
-- العربية
-- 简体中文
-- فارسی
-- Čeština
- Русский
+- Українська
+- العربية
+- فارسی
+- ไทย
+- 한국어
+- 日本語
+- 简体中文
+
+
+
+Can't find your language? Learn how you can contribute by translating the admin to other languages [here](!docs!/learn/resources/contribution-guidelines/admin-translations).
+
+
\ No newline at end of file
diff --git a/www/apps/user-guide/generated/edit-dates.mjs b/www/apps/user-guide/generated/edit-dates.mjs
index f3d20eac4d..9098cd2ce7 100644
--- a/www/apps/user-guide/generated/edit-dates.mjs
+++ b/www/apps/user-guide/generated/edit-dates.mjs
@@ -1,5 +1,5 @@
export const generatedEditDates = {
- "app/tips/languages/page.mdx": "2025-03-06T08:49:32.514Z",
+ "app/tips/languages/page.mdx": "2025-04-11T09:14:21.012Z",
"app/tips/bulk-editor/page.mdx": "2025-04-07T13:20:09.040Z",
"app/tips/lists/page.mdx": "2025-02-14T14:16:04.867Z",
"app/settings/sales-channels/page.mdx": "2025-02-19T17:33:55.584Z",
@@ -29,7 +29,7 @@ export const generatedEditDates = {
"app/customers/page.mdx": "2025-02-18T15:38:17.543Z",
"app/orders/edit/page.mdx": "2025-02-17T15:15:53.489Z",
"app/products/collections/page.mdx": "2025-02-18T14:00:51.469Z",
- "app/customers/manage/page.mdx": "2025-02-19T10:20:05.606Z",
+ "app/customers/manage/page.mdx": "2025-04-11T09:30:20.738Z",
"app/discounts/create/page.mdx": "2024-05-03T17:36:38+03:00",
"app/orders/payments/page.mdx": "2025-02-17T15:10:24.250Z",
"app/discounts/page.mdx": "2024-05-03T17:36:38+03:00",