diff --git a/www/apps/book/app/learn/fundamentals/api-routes/middlewares/page.mdx b/www/apps/book/app/learn/fundamentals/api-routes/middlewares/page.mdx
index 7d69c2eb4ee52..fc488afe14e53 100644
--- a/www/apps/book/app/learn/fundamentals/api-routes/middlewares/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/api-routes/middlewares/page.mdx
@@ -18,11 +18,20 @@ As Medusa's server is based on Express, you can use any [Express middleware](htt
+### Middleware Types
+
+There are two types of middlewares:
+
+1. Global Middleware: A middleware that applies to all routes matching a specified pattern.
+2. Route Middleware: A middleware that applies to routes matching a specified pattern and HTTP method(s).
+
+These middlewares generally have the same definition and usage, but they differ in the routes they apply to. You'll learn how to create both types in the following sections.
+
---
-## How to Create a Middleware?
+## How to Create a Global Middleware?
-Middlewares are defined in the special file `src/api/middlewares.ts`. Use the `defineMiddlewares` function from the Medusa Framework to define the middlewares, and export its value.
+Middlewares of all types are defined in the special file `src/api/middlewares.ts`. Use the `defineMiddlewares` function from the Medusa Framework to define the middlewares, and export its value.
For example:
@@ -57,13 +66,69 @@ export default defineMiddlewares({
The `defineMiddlewares` function accepts a middleware configurations object that has the property `routes`. `routes`'s value is an array of middleware route objects, each having the following properties:
- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. The regular expression must be compatible with [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
-- `middlewares`: An array of middleware functions.
+- `middlewares`: An array of global and route middleware functions.
+
+In the example above, you define a global middleware that logs the message `Received a request!` whenever a request is sent to an API route path starting with `/custom`.
+
+### Test the Global Middleware
+
+To test the middleware:
+
+1. Start the application:
+
+```bash npm2yarn
+npm run dev
+```
-In the example above, you define a middleware that logs the message `Received a request!` whenever a request is sent to an API route path starting with `/custom`.
+2. Send a request to any API route starting with `/custom`.
+3. See the following message in the terminal:
+
+```bash
+Received a request!
+```
---
-## Test the Middleware
+## How to Create a Route Middleware?
+
+In the previous section, you learned how to create a global middleware. You define the route middleware in the same way in `src/api/middlewares.ts`, but you specify an additional property `method` in the middleware route object. Its value is one or more HTTP methods to apply the middleware to.
+
+For example:
+
+export const highlights = [["12", "method", "Apply the middleware only on `POST` requests"]]
+
+```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import {
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom*",
+ method: ["POST", "PUT"],
+ middlewares: [
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ console.log("Received a request!")
+
+ next()
+ },
+ ],
+ },
+ ],
+})
+```
+
+This example applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/custom`, changing the middleware from a global middleware to a route middleware.
+
+### Test the Route Middleware
To test the middleware:
@@ -73,7 +138,7 @@ To test the middleware:
npm run dev
```
-2. Send a request to any API route starting with `/custom`.
+2. Send a `POST` request to any API route starting with `/custom`.
3. See the following message in the terminal:
```bash
@@ -141,15 +206,13 @@ This applies a middleware to the routes defined in the file `src/api/custom/[id]
---
-## Restrict HTTP Methods
-
-Restrict which HTTP methods the middleware is applied to using the `method` property of the middleware route object.
+## Request URLs with Trailing Backslashes
-For example:
+A middleware whose `matcher` pattern doesn't end with a backslash won't be applied for requests to URLs with a trailing backslash.
-export const highlights = [["12", "method", "Apply the middleware only on `POST` requests"]]
+For example, consider you have the following middleware:
-```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+```ts title="src/api/middlewares.ts" collapsibleLines="1-7" expandMoreLabel="Show Imports"
import {
MedusaNextFunction,
MedusaRequest,
@@ -160,48 +223,126 @@ import {
export default defineMiddlewares({
routes: [
{
- matcher: "/custom*",
- method: ["POST", "PUT"],
+ matcher: "/custom",
middlewares: [
- // ...
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ console.log("Received a request!")
+
+ next()
+ },
],
},
],
})
```
-`method`'s value is one or more HTTP methods to apply the middleware to.
+If you send a request to `http://localhost:9000/custom`, the middleware will run.
+
+However, if you send a request to `http://localhost:9000/custom/`, the middleware won't run.
-This example applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/custom`.
+In general, avoid adding trailing backslashes when sending requests to API routes.
---
-## Request URLs with Trailing Backslashes
+## Middlewares and Route Ordering
-A middleware whose `matcher` pattern doesn't end with a backslash won't be applied for requests to URLs with a trailing backslash.
+
-For example, consider you have the following middleware:
+The ordering explained in this section was added in [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6)
-```ts collapsibleLines="1-7" expandMoreLabel="Show Imports"
-import {
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
- defineMiddlewares,
-} from "@medusajs/framework/http"
+
+
+The Medusa application registers middlewares and API route handlers in the following order:
+
+1. Global middlewares in the following order:
+ 1. Global middleware defined in the Medusa's core.
+ 2. Global middleware defined in the plugins (in the order the plugins are registered in).
+ 3. Global middleware you define in the application.
+2. Route middlewares in the following order:
+ 1. Route middleware defined in the Medusa's core.
+ 2. Route middleware defined in the plugins (in the order the plugins are registered in).
+ 3. Route middleware you define in the application.
+3. API routes in the following order:
+ 1. API routes defined in the Medusa's core.
+ 2. API routes defined in the plugins (in the order the plugins are registered in).
+ 3. API routes you define in the application.
+
+### Middlewares Sorting
+
+On top of the previous ordering, Medusa sorts global and route middlewares based on their matcher pattern in the following order:
+1. Wildcard matchers. For example, `/custom*`.
+2. Regex matchers. For example, `/custom/(products|collections)`.
+3. Static matchers without parameters. For example, `/custom`.
+4. Static matchers with parameters. For example, `/custom/:id`.
+
+For example, if you have the following middlewares:
+
+```ts title="src/api/middlewares.ts"
export default defineMiddlewares({
routes: [
+ {
+ matcher: "/custom/:id",
+ middlewares: [/* ... */],
+ },
{
matcher: "/custom",
- middlewares: [
- (
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- console.log("Received a request!")
+ middlewares: [/* ... */],
+ },
+ {
+ matcher: "/custom*",
+ method: ["GET"],
+ middlewares: [/* ... */],
+ },
+ {
+ matcher: "/custom/:id",
+ method: ["GET"],
+ middlewares: [/* ... */],
+ },
+ ],
+})
+```
+
+The global middlewares are sorted into the following order before they're registered:
+
+1. Global middleware `/custom`.
+2. Global middleware `/custom/:id`.
+
+And the route middlewares are sorted into the following order before they're registered:
+
+1. Route middleware `/custom*`.
+2. Route middleware `/custom/:id`.
+
+Then, the middlwares are registered in the order mentioned earlier, with global middlewares first, then the route middlewares.
+
+### Middlewares and Route Execution Order
+
+When a request is sent to an API route, the global middlewares are executed first, then the route middlewares, and finally the route handler.
+
+For example, consider you have the following middlewares:
+```ts title="src/api/middlewares.ts"
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom",
+ middlewares: [
+ (req, res, next) => {
+ console.log("Global middleware")
+ next()
+ },
+ ],
+ },
+ {
+ matcher: "/custom",
+ method: ["GET"],
+ middlewares: [
+ (req, res, next) => {
+ console.log("Route middleware")
next()
},
],
@@ -210,16 +351,20 @@ export default defineMiddlewares({
})
```
-If you send a request to `http://localhost:9000/custom`, the middleware will run.
+When you send a request to `/custom` route, the following messages are logged in the terminal:
-However, if you send a request to `http://localhost:9000/custom/`, the middleware won't run.
+```bash
+Global middleware
+Route middleware
+Hello from custom! # message logged from API route handler
+```
-In general, avoid adding trailing backslashes when sending requests to API routes.
+The global middleware runs first, then the route middleware, and finally the route handler, assuming that it logs the message `Hello from custom!`.
---
-## Middlewares Precedence in Registration
+## Overriding Middlewares
-The Medusa application registers your middlewares first, then registers middlewares defined in Medusa's core.
+A middleware can not override an existing middleware. Instead, middlewares are added to the end of the middleware stack.
-So, if you add a middleware for a route defined in the core, it might get overridden by the core middleware. For example, if you add a middleware to change authentication of admin routes, the authentication middleware defined in the core will still run, leading to your middleware not being effective.
+For example, if you define a custom validation middleware, such as `validateAndTransformBody`, on an existing route, then both the original and the custom validation middleware will run.
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index 07251de07f59a..baf4b59610006 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -53,7 +53,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/admin/tips/page.mdx": "2025-02-24T09:52:06.901Z",
"app/learn/fundamentals/api-routes/cors/page.mdx": "2024-12-09T13:04:04.357Z",
"app/learn/fundamentals/admin/ui-routes/page.mdx": "2025-02-24T09:35:11.752Z",
- "app/learn/fundamentals/api-routes/middlewares/page.mdx": "2025-02-12T17:05:20.708Z",
+ "app/learn/fundamentals/api-routes/middlewares/page.mdx": "2025-03-04T10:16:15.029Z",
"app/learn/fundamentals/modules/isolation/page.mdx": "2024-12-09T11:02:38.087Z",
"app/learn/fundamentals/data-models/configure-properties/page.mdx": "2024-10-21T13:30:21.368Z",
"app/learn/fundamentals/data-models/index/page.mdx": "2024-10-21T13:30:21.368Z",
diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt
index 39b1b40bee34a..f802012bbe76f 100644
--- a/www/apps/book/public/llms-full.txt
+++ b/www/apps/book/public/llms-full.txt
@@ -312,35 +312,6 @@ Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan
Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers.
-# More Resources
-
-The Development Resources documentation provides guides and references that are useful for your development. This documentation included links to parts of the Development Resources documentation where necessary.
-
-Check out the Development Resources documentation [here](https://docs.medusajs.com/resources/index.html.md).
-
-
-# Storefront Development
-
-The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, 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.
-
-You can build your storefront from scratch with your preferred tech stack, or start with our Next.js Starter storefront. 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.
-
-- [Install Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md)
-- [Build Custom Storefront](https://docs.medusajs.com/resources/storefront-development/index.html.md)
-
-***
-
-## Passing a Publishable API Key in Storefront Requests
-
-When sending a request to an API route starting with `/store`, you must include a publishable API key in the header of your request.
-
-A publishable API key sets the scope of your request to one or more sales channels.
-
-Then, when you retrieve products, only products of those sales channels are retrieved. This also ensures you retrieve correct inventory data, and associate created orders with the scoped sales channel.
-
-Learn more about passing the publishable API key in [this storefront development guide](https://docs.medusajs.com/resources/storefront-development/publishable-api-keys/index.html.md).
-
-
# Debugging and Testing
In the next chapters, you’ll learn about the tools Medusa provides for testing and debugging your Medusa application.
@@ -351,6 +322,13 @@ By the end of this chapter, you’ll learn:
- How to use Medusa’s `Logger` utility to log messages.
+# More Resources
+
+The Development Resources documentation provides guides and references that are useful for your development. This documentation included links to parts of the Development Resources documentation where necessary.
+
+Check out the Development Resources documentation [here](https://docs.medusajs.com/resources/index.html.md).
+
+
# Updating Medusa
In this chapter, you'll learn about updating your Medusa application and packages.
@@ -457,6 +435,28 @@ npm install
```
+# Storefront Development
+
+The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, 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.
+
+You can build your storefront from scratch with your preferred tech stack, or start with our Next.js Starter storefront. 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.
+
+- [Install Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md)
+- [Build Custom Storefront](https://docs.medusajs.com/resources/storefront-development/index.html.md)
+
+***
+
+## Passing a Publishable API Key in Storefront Requests
+
+When sending a request to an API route starting with `/store`, you must include a publishable API key in the header of your request.
+
+A publishable API key sets the scope of your request to one or more sales channels.
+
+Then, when you retrieve products, only products of those sales channels are retrieved. This also ensures you retrieve correct inventory data, and associate created orders with the scoped sales channel.
+
+Learn more about passing the publishable API key in [this storefront development guide](https://docs.medusajs.com/resources/storefront-development/publishable-api-keys/index.html.md).
+
+
# Using TypeScript Aliases
By default, Medusa doesn't support TypeScript aliases in production.
@@ -526,6 +526,30 @@ The next chapters will guide you to:
3. Expose an API route that allows admin users to create a brand using the workflow.
+# 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.
+
+The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals.
+
+## Helpful Resources Guides
+
+The [Development Resources](https://docs.medusajs.com/resources/index.html.md) documentation provides more helpful guides and references for your development journey. Some of these guides and references include:
+
+3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them.
+4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples.
+5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations.
+6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets.
+
+***
+
+## More Examples in Recipes
+
+In the Development Resources documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more.
+
+Refer to the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation to learn more.
+
+
# Customize Medusa Admin Dashboard
In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md).
@@ -570,28 +594,27 @@ The next chapters explain how to use the tools mentioned above with step-by-step
- Retrieve a product's associated brand's details.
-# 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.
+# Integrate Third-Party Systems
-The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals.
+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.
-## Helpful Resources Guides
+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.
-The [Development Resources](https://docs.medusajs.com/resources/index.html.md) documentation provides more helpful guides and references for your development journey. Some of these guides and references include:
+In Medusa, you integrate a third-party system by:
-3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them.
-4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples.
-5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations.
-6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets.
+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.
***
-## More Examples in Recipes
+## Next Chapters: Sync Brands Example
-In the Development Resources documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more.
+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:
-Refer to the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation to learn more.
+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.
# Re-Use Customizations with Plugins
@@ -609,6 +632,64 @@ 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).
+# Medusa's Architecture
+
+In this chapter, you'll learn about the architectural layers in Medusa.
+
+## 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.
+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 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).
+
+
+
+***
+
+## Service Integrations
+
+Third-party services are integrated through commerce and architectural modules. You also create custom third-party integrations through a custom module.
+
+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 integrate Stripe through a payment module provider.
+
+
+
+### Architectural Modules
+
+[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. For example, you integrate Redis as a pub/sub service to send events, or SendGrid to send notifications.
+
+
+
+***
+
+## Full Diagram of Medusa's Architecture
+
+The following diagram illustrates Medusa's architecture over the three layers.
+
+
+
+
# 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.
@@ -910,29 +991,6 @@ Replace the email `admin-medusa@test.com` and password `supersecret` with the cr
You can use these credentials to log into the Medusa Admin dashboard.
-# 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.
-
-
# Admin Development
In the next chapters, you'll learn more about possible admin customizations.
@@ -957,76 +1015,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.
-# Custom CLI Scripts
-
-In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
-
-## What is a Custom CLI Script?
-
-A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI.
-
-***
-
-## How to Create a Custom CLI Script?
-
-To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
-
-For example, create the file `src/scripts/my-script.ts` with the following content:
-
-```ts title="src/scripts/my-script.ts"
-import {
- ExecArgs,
- IProductModuleService,
-} from "@medusajs/framework/types"
-import { Modules } from "@medusajs/framework/utils"
-
-export default async function myScript({ container }: ExecArgs) {
- const productModuleService: IProductModuleService = container.resolve(
- Modules.PRODUCT
- )
-
- const [, count] = await productModuleService
- .listAndCountProducts()
-
- console.log(`You have ${count} product(s)`)
-}
-```
-
-The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
-
-***
-
-## How to Run Custom CLI Script?
-
-To run the custom CLI script, run the Medusa CLI's `exec` command:
-
-```bash
-npx medusa exec ./src/scripts/my-script.ts
-```
-
-***
-
-## Custom CLI Script Arguments
-
-Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
-
-For example:
-
-```ts
-import { ExecArgs } from "@medusajs/framework/types"
-
-export default async function myScript({ args }: ExecArgs) {
- console.log(`The arguments you passed: ${args}`)
-}
-```
-
-Then, pass the arguments in the `exec` command after the file path:
-
-```bash
-npx medusa exec ./src/scripts/my-script.ts arg1 arg2
-```
-
-
# API Routes
In this chapter, you’ll learn what API Routes are and how to create them.
@@ -1165,6 +1153,178 @@ You should opt for setting configurations in `medusa-config.ts` where possible.
||Whether to disable analytics data collection. Learn more in ||
+# Events and Subscribers
+
+In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers.
+
+## Handle Core Commerce Flows with Events
+
+When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system.
+
+Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase.
+
+You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted.
+
+
+
+Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it.
+
+If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead.
+
+### List of Emitted Events
+
+Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md).
+
+***
+
+## How to Create a Subscriber?
+
+You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to.
+
+For example, create the file `src/subscribers/order-placed.ts` with the following content:
+
+
+
+```ts title="src/subscribers/product-created.ts"
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
+
+export default async function orderPlacedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const logger = container.resolve("logger")
+
+ logger.info("Sending confirmation email...")
+
+ await sendOrderConfirmationWorkflow(container)
+ .run({
+ input: {
+ id: data.id,
+ },
+ })
+}
+
+export const config: SubscriberConfig = {
+ event: `order.placed`,
+}
+```
+
+This subscriber file exports:
+
+- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered.
+- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber.
+
+The subscriber function receives an object as a parameter that has the following properties:
+
+- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case.
+- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources.
+
+In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber.
+
+***
+
+## Test the Subscriber
+
+To test the subscriber, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal:
+
+```bash
+info: Processing order.placed which has 1 subscribers
+Sending confirmation email...
+```
+
+The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber.
+
+***
+
+## Event Module
+
+The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system.
+
+Medusa provides two Event Modules out of the box:
+
+- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it.
+- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system.
+
+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).
+
+
+# Custom CLI Scripts
+
+In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
+
+## What is a Custom CLI Script?
+
+A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI.
+
+***
+
+## How to Create a Custom CLI Script?
+
+To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
+
+For example, create the file `src/scripts/my-script.ts` with the following content:
+
+```ts title="src/scripts/my-script.ts"
+import {
+ ExecArgs,
+ IProductModuleService,
+} from "@medusajs/framework/types"
+import { Modules } from "@medusajs/framework/utils"
+
+export default async function myScript({ container }: ExecArgs) {
+ const productModuleService: IProductModuleService = container.resolve(
+ Modules.PRODUCT
+ )
+
+ const [, count] = await productModuleService
+ .listAndCountProducts()
+
+ console.log(`You have ${count} product(s)`)
+}
+```
+
+The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
+
+***
+
+## How to Run Custom CLI Script?
+
+To run the custom CLI script, run the Medusa CLI's `exec` command:
+
+```bash
+npx medusa exec ./src/scripts/my-script.ts
+```
+
+***
+
+## Custom CLI Script Arguments
+
+Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
+
+For example:
+
+```ts
+import { ExecArgs } from "@medusajs/framework/types"
+
+export default async function myScript({ args }: ExecArgs) {
+ console.log(`The arguments you passed: ${args}`)
+}
+```
+
+Then, pass the arguments in the `exec` command after the file path:
+
+```bash
+npx medusa exec ./src/scripts/my-script.ts arg1 arg2
+```
+
+
# Medusa Container
In this chapter, you’ll learn about the Medusa container and how to use it.
@@ -1315,121 +1475,6 @@ A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md),
Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
-# Events and Subscribers
-
-In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers.
-
-## Handle Core Commerce Flows with Events
-
-When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system.
-
-Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase.
-
-You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted.
-
-
-
-Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it.
-
-If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead.
-
-### List of Emitted Events
-
-Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md).
-
-***
-
-## How to Create a Subscriber?
-
-You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to.
-
-For example, create the file `src/subscribers/order-placed.ts` with the following content:
-
-
-
-```ts title="src/subscribers/product-created.ts"
-import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
-import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation"
-
-export default async function orderPlacedHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const logger = container.resolve("logger")
-
- logger.info("Sending confirmation email...")
-
- await sendOrderConfirmationWorkflow(container)
- .run({
- input: {
- id: data.id,
- },
- })
-}
-
-export const config: SubscriberConfig = {
- event: `order.placed`,
-}
-```
-
-This subscriber file exports:
-
-- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered.
-- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber.
-
-The subscriber function receives an object as a parameter that has the following properties:
-
-- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case.
-- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources.
-
-In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber.
-
-***
-
-## Test the Subscriber
-
-To test the subscriber, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal:
-
-```bash
-info: Processing order.placed which has 1 subscribers
-Sending confirmation email...
-```
-
-The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber.
-
-***
-
-## Event Module
-
-The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system.
-
-Medusa provides two Event Modules out of the box:
-
-- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it.
-- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system.
-
-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).
-
-
-# Data Models Advanced Guides
-
-Data models are created and managed in a module. To learn how to create a data model in a custom module, refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-In the next chapters, you'll learn about defining data models in more details. You'll learn about:
-
-- The different property types available.
-- How to set a property as a primary key.
-- How to create and manage relationships.
-- How to configure properties, such as making them nullable or searchable.
-- How to manually write migrations.
-
-
# Module Link
In this chapter, you’ll learn what a module link is.
@@ -1554,6 +1599,113 @@ export default defineLink(
In this example, when a product is deleted, its linked `myCustom` record is also deleted.
+# Data Models Advanced Guides
+
+Data models are created and managed in a module. To learn how to create a data model in a custom module, refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+
+In the next chapters, you'll learn about defining data models in more details. You'll learn about:
+
+- The different property types available.
+- How to set a property as a primary key.
+- How to create and manage relationships.
+- How to configure properties, such as making them nullable or searchable.
+- How to manually write migrations.
+
+
+# Scheduled Jobs
+
+In this chapter, you’ll learn about scheduled jobs and how to use them.
+
+## What is a Scheduled Job?
+
+When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day.
+
+In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling.
+
+Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP.
+
+- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
+- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead.
+- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead.
+
+***
+
+## How to Create a Scheduled Job?
+
+You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function.
+
+For example, create the file `src/jobs/hello-world.ts` with the following content:
+
+
+
+```ts title="src/jobs/hello-world.ts" highlights={highlights}
+import { MedusaContainer } from "@medusajs/framework/types"
+
+export default async function greetingJob(container: MedusaContainer) {
+ const logger = container.resolve("logger")
+
+ logger.info("Greeting!")
+}
+
+export const config = {
+ name: "greeting-every-minute",
+ schedule: "* * * * *",
+}
+```
+
+You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message.
+
+You also export a `config` object that has the following properties:
+
+- `name`: A unique name for the job.
+- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job.
+
+This scheduled job executes every minute and logs into the terminal `Greeting!`.
+
+### Test the Scheduled Job
+
+To test out your scheduled job, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+After a minute, the following message will be logged to the terminal:
+
+```bash
+info: Greeting!
+```
+
+***
+
+## Example: Sync Products Once a Day
+
+In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service.
+
+When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more.
+
+You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content:
+
+```ts title="src/jobs/sync-products.ts"
+import { MedusaContainer } from "@medusajs/framework/types"
+import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp"
+
+export default async function syncProductsJob(container: MedusaContainer) {
+ await syncProductToErpWorkflow(container)
+ .run()
+}
+
+export const config = {
+ name: "sync-products-job",
+ schedule: "0 0 * * *",
+}
+```
+
+In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day.
+
+The next time you start the Medusa application, it will run this job every day at midnight.
+
+
# Plugins
In this chapter, you'll learn what a plugin is in Medusa.
@@ -1598,90 +1750,344 @@ The next chapter explains how you can create and publish a plugin.
For more resources and guides related to plugins, refer to the [Resources documentation](https://docs.medusajs.com/resources/plugins/index.html.md).
-# Modules
+# Workflows
-In this chapter, you’ll learn about modules and how to create them.
+In this chapter, you’ll learn about workflows and how to define and execute them.
-## What is a Module?
+## What is a Workflow?
-A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations.
+In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems.
-When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations.
+Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly.
-Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without implications on the existing setup. You can also re-use your modules across Medusa projects.
+A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.
-As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem.
+However, unlike regular functions, workflows:
-- You want to build a custom feature related to a single domain or integrate a third-party service.
+- Create an internal representation of your steps, allowing you to track them and their progress.
+- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems.
+- Perform long actions asynchronously, giving you control over when a step starts and finishes.
-- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
***
-## How to Create a Module?
+## How to Create and Execute a Workflow?
-In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module.
+### 1. Create the Steps
-In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model, you'll expose an API endpoint to create a blog post.
+A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK.
-Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`.
+Create the file `src/workflows/hello-world.ts` with the following content:
-### 1. Create Data Model
+
-A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
+```ts title="src/workflows/hello-world.ts" highlights={step1Highlights}
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content:
+const step1 = createStep(
+ "step-1",
+ async () => {
+ return new StepResponse(`Hello from step one!`)
+ }
+)
+```
-
+The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter.
-```ts title="src/modules/blog/models/post.ts"
-import { model } from "@medusajs/framework/utils"
+Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step.
-const Post = model.define("post", {
- id: model.id().primaryKey(),
- title: model.text(),
-})
+Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`:
-export default Post
+```ts title="src/workflows/hello-world.ts" highlights={step2Highlights}
+type WorkflowInput = {
+ name: string
+}
+
+const step2 = createStep(
+ "step-2",
+ async ({ name }: WorkflowInput) => {
+ return new StepResponse(`Hello ${name} from step two!`)
+ }
+)
```
-You define the data model using the `define` method of the DML. It accepts two parameters:
+This adds another step whose function accepts as a parameter an object with a `name` property.
-1. The first one is the name of the data model's table in the database. Use snake-case names.
-2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
- - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
+### 2. Create a Workflow
-Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md).
+Next, add the following to the same file to create the workflow using the `createWorkflow` function:
-The code snippet above defines a `Post` data model with `id` and `title` properties.
+```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights}
+import {
+ // other imports...
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
-### 2. Create Service
+// ...
-You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows.
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const str1 = step1()
+ // to pass input
+ const str2 = step2(input)
-You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content:
+ return new WorkflowResponse({
+ message: str2,
+ })
+ }
+)
-
+export default myWorkflow
+```
-```ts title="src/modules/blog/service.ts" highlights={highlights}
-import { MedusaService } from "@medusajs/framework/utils"
-import Post from "./models/post"
+The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.
-class BlogModuleService extends MedusaService({
- Post,
-}){
-}
+The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors.
-export default BlogModuleService
-```
+### 3. Execute the Workflow
-Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving your time that can be spent on building custom business logic.
+You can execute a workflow from different customizations:
-The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object.
+- Execute in an API route to expose the workflow's functionalities to clients.
+- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed.
+- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval.
-For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model.
+To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method:
-Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} 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({
+ input: {
+ name: "John",
+ },
+ })
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import myWorkflow from "../workflows/hello-world"
+
+export default async function handleOrderPlaced({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await myWorkflow(container)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "order.placed",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import myWorkflow from "../workflows/hello-world"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await myWorkflow(container)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+
+ console.log(result.message)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+};
+```
+
+### 4. Test Workflow
+
+To test out your workflow, start your Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, if you added the API route above, send a `GET` request to `/workflow`:
+
+```bash
+curl http://localhost:9000/workflow
+```
+
+You’ll receive the following response:
+
+```json title="Example Response"
+{
+ "message": "Hello John from step two!"
+}
+```
+
+***
+
+## Access Medusa Container in Workflow Steps
+
+A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application.
+
+For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content:
+
+```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ StepResponse,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const getProductCountStep = createStep(
+ "get-product-count",
+ async (_, { container }) => {
+ const productModuleService = container.resolve("product")
+
+ const [, count] = await productModuleService.listAndCountProducts()
+
+ return new StepResponse(count)
+ }
+)
+
+const productCountWorkflow = createWorkflow(
+ "product-count",
+ function () {
+ const count = getProductCountStep()
+
+ return new WorkflowResponse({
+ count,
+ })
+ }
+)
+
+export default productCountWorkflow
+```
+
+In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`.
+
+You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.
+
+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.
+
+
+# Modules
+
+In this chapter, you’ll learn about modules and how to create them.
+
+## What is a Module?
+
+A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations.
+
+When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations.
+
+Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without implications on the existing setup. You can also re-use your modules across Medusa projects.
+
+As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem.
+
+- You want to build a custom feature related to a single domain or integrate a third-party service.
+
+- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+
+***
+
+## How to Create a Module?
+
+In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module.
+
+In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model, you'll expose an API endpoint to create a blog post.
+
+Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`.
+
+### 1. Create Data Model
+
+A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
+
+You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content:
+
+
+
+```ts title="src/modules/blog/models/post.ts"
+import { model } from "@medusajs/framework/utils"
+
+const Post = model.define("post", {
+ id: model.id().primaryKey(),
+ title: model.text(),
+})
+
+export default Post
+```
+
+You define the data model using the `define` method of the DML. It accepts two parameters:
+
+1. The first one is the name of the data model's table in the database. Use snake-case names.
+2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`.
+ - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually.
+
+Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md).
+
+The code snippet above defines a `Post` data model with `id` and `title` properties.
+
+### 2. Create Service
+
+You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows.
+
+You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content:
+
+
+
+```ts title="src/modules/blog/service.ts" highlights={highlights}
+import { MedusaService } from "@medusajs/framework/utils"
+import Post from "./models/post"
+
+class BlogModuleService extends MedusaService({
+ Post,
+}){
+}
+
+export default BlogModuleService
+```
+
+Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving your time that can be spent on building custom business logic.
+
+The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object.
+
+For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model.
+
+Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
If a module doesn't have data models, such as when it's integrating a third-party service, it doesn't need to extend `MedusaService`.
@@ -1898,603 +2304,271 @@ 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.
-# Medusa's Architecture
+# Configure Instrumentation
-In this chapter, you'll learn about the architectural layers in Medusa.
+In this chapter, you'll learn about observability in Medusa and how to configure instrumentation with OpenTelemetry.
-## HTTP, Workflow, and Module Layers
+## Observability with OpenTelemtry
-Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes.
+Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and reporting. When configured, it reports traces for:
-In a common Medusa application, requests go through four layers in the stack. In order of entry, those are:
+- HTTP requests
+- Workflow executions
+- Query usages
+- Database queries and operations
-1. API Routes (HTTP): Our API Routes are the typical entry point.
-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).
+## How to Configure Instrumentation in Medusa?
-
+### Prerequisites
-***
+- [An exporter to visualize your application's traces, such as Zipkin.](https://zipkin.io/pages/quickstart.html)
-## Database Layer
+### Install Dependencies
-The Medusa application injects into each module a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database.
+Start by installing the following OpenTelemetry dependencies in your Medusa project:
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+```bash npm2yarn
+npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg
+```
-
+Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
-***
+```bash npm2yarn
+npm install @opentelemetry/exporter-zipkin
+```
-## Service Integrations
+### Add instrumentation.ts
-Third-party services are integrated through commerce and architectural modules. You also create custom third-party integrations through a custom module.
+Next, create the file `instrumentation.ts` with the following content:
-Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+```ts title="instrumentation.ts"
+import { registerOtel } from "@medusajs/medusa"
+import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"
-### Commerce Modules
+// If using an exporter other than Zipkin, initialize it here.
+const exporter = new ZipkinExporter({
+ serviceName: "my-medusa-project",
+})
-[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 integrate Stripe through a payment module provider.
+export function register() {
+ registerOtel({
+ serviceName: "medusajs",
+ // pass exporter
+ exporter,
+ instrument: {
+ http: true,
+ workflows: true,
+ query: true,
+ },
+ })
+}
+```
-
+In the `instrumentation.ts` file, you export a `register` function that uses Medusa's `registerOtel` utility function. You also initialize an instance of the exporter, such as Zipkin, and pass it to the `registerOtel` function.
-### Architectural Modules
+`registerOtel` accepts an object where you can pass any [NodeSDKConfiguration](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_node.NodeSDKConfiguration.html) property along with the following properties:
-[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. For example, you integrate Redis as a pub/sub service to send events, or SendGrid to send notifications.
+The `NodeSDKConfiguration` properties are accepted since Medusa v2.5.1.
-
+- serviceName: (\`string\`) The name of the service traced.
+- exporter: (\[SpanExporter]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_sdk\_trace\_base.SpanExporter.html)) An instance of an exporter, such as Zipkin.
+- instrument: (\`object\`) Options specifying what to trace.
+
+ - http: (\`boolean\`) Whether to trace HTTP requests.
+
+ - query: (\`boolean\`) Whether to trace Query usages.
+
+ - workflows: (\`boolean\`) Whether to trace Workflow executions.
+
+ - db: (\`boolean\`) Whether to trace database queries and operations.
+- instrumentations: (\[Instrumentation\[]]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_instrumentation.Instrumentation.html)) Additional instrumentation options that OpenTelemetry accepts.
***
-## Full Diagram of Medusa's Architecture
+## Test it Out
-The following diagram illustrates Medusa's architecture over the three layers.
+To test it out, start your exporter, such as Zipkin.
-
+Then, start your Medusa application:
+```bash npm2yarn
+npm run dev
+```
-# Scheduled Jobs
+Try to open the Medusa Admin or send a request to an API route.
-In this chapter, you’ll learn about scheduled jobs and how to use them.
+If you check traces in your exporter, you'll find new traces reported.
-## What is a Scheduled Job?
+### Trace Span Names
-When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day.
+Trace span names start with the following keywords based on what it's reporting:
-In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling.
+- `{methodName} {URL}` when reporting HTTP requests, where `{methodName}` is the HTTP method, and `{URL}` is the URL the request is sent to.
+- `route:` when reporting route handlers running on an HTTP request.
+- `middleware:` when reporting a middleware running on an HTTP request.
+- `workflow:` when reporting a workflow execution.
+- `step:` when reporting a step in a workflow execution.
+- `query.graph:` when reporting Query usages.
+- `pg.query:` when reporting database queries and operations.
-Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP.
-- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job.
-- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead.
-- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead.
+# Logging
-***
+In this chapter, you’ll learn how to use Medusa’s logging utility.
-## How to Create a Scheduled Job?
+## Logger Class
-You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function.
+Medusa provides a `Logger` class with advanced logging functionalities. This includes configuring logging levels or saving logs to a file.
-For example, create the file `src/jobs/hello-world.ts` with the following content:
+The Medusa application registers the `Logger` class in the Medusa container and each module's container as `logger`.
-
+***
-```ts title="src/jobs/hello-world.ts" highlights={highlights}
+## How to Log a Message
+
+Resolve the `logger` using the Medusa container to log a message in your resource.
+
+For example, create the file `src/jobs/log-message.ts` with the following content:
+
+```ts title="src/jobs/log-message.ts" highlights={highlights}
import { MedusaContainer } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-export default async function greetingJob(container: MedusaContainer) {
- const logger = container.resolve("logger")
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
- logger.info("Greeting!")
+ logger.info("I'm using the logger!")
}
export const config = {
- name: "greeting-every-minute",
+ name: "test-logger",
+ // execute every minute
schedule: "* * * * *",
}
```
-You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message.
-
-You also export a `config` object that has the following properties:
-
-- `name`: A unique name for the job.
-- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job.
-
-This scheduled job executes every minute and logs into the terminal `Greeting!`.
+This creates a scheduled job that resolves the `logger` from the Medusa container and uses it to log a message.
### Test the Scheduled Job
-To test out your scheduled job, start the Medusa application:
+To test out the above scheduled job, start the Medusa application:
```bash npm2yarn
npm run dev
```
-After a minute, the following message will be logged to the terminal:
+After a minute, you'll see the following message as part of the logged messages:
-```bash
-info: Greeting!
+```text
+info: I'm using the logger!
```
***
-## Example: Sync Products Once a Day
-
-In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service.
-
-When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more.
+## Log Levels
-You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content:
+The `Logger` class has the following methods:
-```ts title="src/jobs/sync-products.ts"
-import { MedusaContainer } from "@medusajs/framework/types"
-import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp"
+- `info`: The message is logged with level `info`.
+- `warn`: The message is logged with level `warn`.
+- `error`: The message is logged with level `error`.
+- `debug`: The message is logged with level `debug`.
-export default async function syncProductsJob(container: MedusaContainer) {
- await syncProductToErpWorkflow(container)
- .run()
-}
+Each of these methods accepts a string parameter to log in the terminal with the associated level.
-export const config = {
- name: "sync-products-job",
- schedule: "0 0 * * *",
-}
-```
+***
-In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day.
+## Logging Configurations
-The next time you start the Medusa application, it will run this job every day at midnight.
+### Log Level
+The available log levels, from lowest to highest levels, are:
-# Next.js Starter Storefront
+1. `silly` (default, meaning messages of all levels are logged)
+2. `debug`
+3. `info`
+4. `warn`
+5. `error`
-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.
+You can change that by setting the `LOG_LEVEL` environment variable to the minimum level you want to be logged.
-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.
+For example:
-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).
+```bash
+LOG_LEVEL=error
+```
-## Install Next.js Starter
+This logs `error` messages only.
-### Prerequisites
+The environment variable must be set as a system environment variable and not in `.env`.
-- [Node.js v20+](https://nodejs.org/en/download)
-- [Git CLI tool](https://git-scm.com/downloads)
+### Save Logs in a File
-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:
+Aside from showing the logs in the terminal, you can save the logs in a file by setting the `LOG_FILE` environment variable to the path of the file relative to the Medusa server’s root directory.
-1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
+For example:
```bash
-git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
+LOG_FILE=all.log
```
-2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
+Your logs are now saved in the `all.log` file at the root of your Medusa application.
-```bash npm2yarn
-cd my-medusa-storefront
-npm install
-mv .env.template .env.local
-```
+The environment variable must be set as a system environment variable and not in `.env`.
-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...
-```
+## Show Log with Progress
-4. While the Medusa application is running, start the Next.js Starter storefront:
+The `Logger` class has an `activity` method used to log a message of level `info`. If the Medusa application is running in a development environment, a spinner starts to show the activity's progress.
-```bash npm2yarn
-npm run dev
-```
+For example:
-Your Next.js Starter storefront is now running at `http://localhost:8000`.
+```ts title="src/jobs/log-message.ts"
+import { MedusaContainer } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-***
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-## Customize Storefront
+ const activityId = logger.activity("First log message")
-To customize the storefront, refer to the following directories:
+ logger.progress(activityId, `Second log message`)
-- `src/app`: The storefront’s pages.
-- `src/modules`: The storefront’s components.
-- `src/styles`: The storefront’s styles.
+ logger.success(activityId, "Last log message")
+}
+```
-You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
+The `activity` method returns the ID of the started activity. This ID can then be passed to one of the following methods of the `Logger` class:
-***
+- `progress`: Log a message of level `info` that indicates progress within that same activity.
+- `success`: Log a message of level `info` that indicates that the activity has succeeded. This also ends the associated activity.
+- `failure`: Log a message of level `error` that indicates that the activity has failed. This also ends the associated activity.
-## Configurations and Integrations
+If you configured the `LOG_LEVEL` environment variable to a level higher than those associated with the above methods, their messages won’t be logged.
-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.
+# Medusa Testing Tools
+In this chapter, you'll learn about Medusa's testing tools and how to install and configure them.
-# Workflows
+## @medusajs/test-utils Package
-In this chapter, you’ll learn about workflows and how to define and execute them.
+Medusa provides a Testing Framework to create integration tests for your custom API routes, modules, or other Medusa customizations.
-## What is a Workflow?
+To use the Testing Framework, install `@medusajs/test-utils` as a `devDependency`:
-In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems.
+```bash npm2yarn
+npm install --save-dev @medusajs/test-utils@latest
+```
-Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly.
-
-A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.
-
-However, unlike regular functions, workflows:
-
-- Create an internal representation of your steps, allowing you to track them and their progress.
-- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems.
-- Perform long actions asynchronously, giving you control over when a step starts and finishes.
-
-You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
-
-***
-
-## How to Create and Execute a Workflow?
-
-### 1. Create the Steps
-
-A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK.
-
-Create the file `src/workflows/hello-world.ts` with the following content:
-
-
-
-```ts title="src/workflows/hello-world.ts" highlights={step1Highlights}
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- return new StepResponse(`Hello from step one!`)
- }
-)
-```
-
-The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter.
-
-Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step.
-
-Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`:
-
-```ts title="src/workflows/hello-world.ts" highlights={step2Highlights}
-type WorkflowInput = {
- name: string
-}
-
-const step2 = createStep(
- "step-2",
- async ({ name }: WorkflowInput) => {
- return new StepResponse(`Hello ${name} from step two!`)
- }
-)
-```
-
-This adds another step whose function accepts as a parameter an object with a `name` property.
-
-### 2. Create a Workflow
-
-Next, add the following to the same file to create the workflow using the `createWorkflow` function:
-
-```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights}
-import {
- // other imports...
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-// ...
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const str1 = step1()
- // to pass input
- const str2 = step2(input)
-
- return new WorkflowResponse({
- message: str2,
- })
- }
-)
-
-export default myWorkflow
-```
-
-The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.
-
-The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors.
-
-### 3. Execute the Workflow
-
-You can execute a workflow from different customizations:
-
-- Execute in an API route to expose the workflow's functionalities to clients.
-- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed.
-- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval.
-
-To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method:
-
-### API Route
-
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} 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({
- input: {
- name: "John",
- },
- })
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import myWorkflow from "../workflows/hello-world"
-
-export default async function handleOrderPlaced({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await myWorkflow(container)
- .run({
- input: {
- name: "John",
- },
- })
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "order.placed",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import myWorkflow from "../workflows/hello-world"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await myWorkflow(container)
- .run({
- input: {
- name: "John",
- },
- })
-
- console.log(result.message)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-};
-```
-
-### 4. Test Workflow
-
-To test out your workflow, start your Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, if you added the API route above, send a `GET` request to `/workflow`:
-
-```bash
-curl http://localhost:9000/workflow
-```
-
-You’ll receive the following response:
-
-```json title="Example Response"
-{
- "message": "Hello John from step two!"
-}
-```
-
-***
-
-## Access Medusa Container in Workflow Steps
-
-A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application.
-
-For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content:
-
-```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import {
- createStep,
- StepResponse,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const getProductCountStep = createStep(
- "get-product-count",
- async (_, { container }) => {
- const productModuleService = container.resolve("product")
-
- const [, count] = await productModuleService.listAndCountProducts()
-
- return new StepResponse(count)
- }
-)
-
-const productCountWorkflow = createWorkflow(
- "product-count",
- function () {
- const count = getProductCountStep()
-
- return new WorkflowResponse({
- count,
- })
- }
-)
-
-export default productCountWorkflow
-```
-
-In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`.
-
-You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.
-
-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.
-
-
-# Configure Instrumentation
-
-In this chapter, you'll learn about observability in Medusa and how to configure instrumentation with OpenTelemetry.
-
-## Observability with OpenTelemtry
-
-Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and reporting. When configured, it reports traces for:
-
-- HTTP requests
-- Workflow executions
-- Query usages
-- Database queries and operations
-
-***
-
-## How to Configure Instrumentation in Medusa?
-
-### Prerequisites
-
-- [An exporter to visualize your application's traces, such as Zipkin.](https://zipkin.io/pages/quickstart.html)
-
-### Install Dependencies
-
-Start by installing the following OpenTelemetry dependencies in your Medusa project:
-
-```bash npm2yarn
-npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg
-```
-
-Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
-
-```bash npm2yarn
-npm install @opentelemetry/exporter-zipkin
-```
-
-### Add instrumentation.ts
-
-Next, create the file `instrumentation.ts` with the following content:
-
-```ts title="instrumentation.ts"
-import { registerOtel } from "@medusajs/medusa"
-import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"
-
-// If using an exporter other than Zipkin, initialize it here.
-const exporter = new ZipkinExporter({
- serviceName: "my-medusa-project",
-})
-
-export function register() {
- registerOtel({
- serviceName: "medusajs",
- // pass exporter
- exporter,
- instrument: {
- http: true,
- workflows: true,
- query: true,
- },
- })
-}
-```
-
-In the `instrumentation.ts` file, you export a `register` function that uses Medusa's `registerOtel` utility function. You also initialize an instance of the exporter, such as Zipkin, and pass it to the `registerOtel` function.
-
-`registerOtel` accepts an object where you can pass any [NodeSDKConfiguration](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_node.NodeSDKConfiguration.html) property along with the following properties:
-
-The `NodeSDKConfiguration` properties are accepted since Medusa v2.5.1.
-
-- serviceName: (\`string\`) The name of the service traced.
-- exporter: (\[SpanExporter]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_sdk\_trace\_base.SpanExporter.html)) An instance of an exporter, such as Zipkin.
-- instrument: (\`object\`) Options specifying what to trace.
-
- - http: (\`boolean\`) Whether to trace HTTP requests.
-
- - query: (\`boolean\`) Whether to trace Query usages.
-
- - workflows: (\`boolean\`) Whether to trace Workflow executions.
-
- - db: (\`boolean\`) Whether to trace database queries and operations.
-- instrumentations: (\[Instrumentation\[]]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_instrumentation.Instrumentation.html)) Additional instrumentation options that OpenTelemetry accepts.
-
-***
-
-## Test it Out
-
-To test it out, start your exporter, such as Zipkin.
-
-Then, start your Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Try to open the Medusa Admin or send a request to an API route.
-
-If you check traces in your exporter, you'll find new traces reported.
-
-### Trace Span Names
-
-Trace span names start with the following keywords based on what it's reporting:
-
-- `{methodName} {URL}` when reporting HTTP requests, where `{methodName}` is the HTTP method, and `{URL}` is the URL the request is sent to.
-- `route:` when reporting route handlers running on an HTTP request.
-- `middleware:` when reporting a middleware running on an HTTP request.
-- `workflow:` when reporting a workflow execution.
-- `step:` when reporting a step in a workflow execution.
-- `query.graph:` when reporting Query usages.
-- `pg.query:` when reporting database queries and operations.
-
-
-# Medusa Testing Tools
-
-In this chapter, you'll learn about Medusa's testing tools and how to install and configure them.
-
-## @medusajs/test-utils Package
-
-Medusa provides a Testing Framework to create integration tests for your custom API routes, modules, or other Medusa customizations.
-
-To use the Testing Framework, install `@medusajs/test-utils` as a `devDependency`:
-
-```bash npm2yarn
-npm install --save-dev @medusajs/test-utils@latest
-```
-
-***
+***
## Install and Configure Jest
@@ -2784,6 +2858,144 @@ Now that you have brands in your Medusa application, you want to associate a bra
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
In this chapter, you'll build a Brand Module that adds a `brand` table to the database and provides data-management features for it.
@@ -2940,282 +3152,70 @@ The Brand Module now creates a `brand` table in the database and provides a clas
In the next chapter, you'll implement the functionality to create a brand in a workflow. You'll then use that workflow in a later chapter to expose an endpoint that allows admin users to create a brand.
-# Guide: Create Brand Workflow
+# Next.js Starter Storefront
-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.
+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.
-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 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.
-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.
+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).
-Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
+## Install Next.js Starter
### 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).
+- [Node.js v20+](https://nodejs.org/en/download)
+- [Git CLI tool](https://git-scm.com/downloads)
-To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
+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:
-```ts title="src/workflows/create-brand.ts"
-export const createBrandStep = createStep(
- // ...
- async (id: string, { container }) => {
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
+1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa):
- await brandModuleService.deleteBrands(id)
- }
-)
+```bash
+git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
```
-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)
+2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file:
- return new WorkflowResponse(brand)
- }
-)
+```bash npm2yarn
+cd my-medusa-storefront
+npm install
+mv .env.template .env.local
```
-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.
-
-
-# Logging
-
-In this chapter, you’ll learn how to use Medusa’s logging utility.
-
-## Logger Class
-
-Medusa provides a `Logger` class with advanced logging functionalities. This includes configuring logging levels or saving logs to a file.
-
-The Medusa application registers the `Logger` class in the Medusa container and each module's container as `logger`.
-
-***
-
-## How to Log a Message
-
-Resolve the `logger` using the Medusa container to log a message in your resource.
-
-For example, create the file `src/jobs/log-message.ts` with the following content:
-
-```ts title="src/jobs/log-message.ts" highlights={highlights}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-
- logger.info("I'm using the logger!")
-}
+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
-export const config = {
- name: "test-logger",
- // execute every minute
- schedule: "* * * * *",
-}
+```bash
+NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123...
```
-This creates a scheduled job that resolves the `logger` from the Medusa container and uses it to log a message.
-
-### Test the Scheduled Job
-
-To test out the above scheduled job, start the Medusa application:
+4. While the Medusa application is running, start the Next.js Starter storefront:
```bash npm2yarn
npm run dev
```
-After a minute, you'll see the following message as part of the logged messages:
-
-```text
-info: I'm using the logger!
-```
-
-***
-
-## Log Levels
-
-The `Logger` class has the following methods:
-
-- `info`: The message is logged with level `info`.
-- `warn`: The message is logged with level `warn`.
-- `error`: The message is logged with level `error`.
-- `debug`: The message is logged with level `debug`.
-
-Each of these methods accepts a string parameter to log in the terminal with the associated level.
+Your Next.js Starter storefront is now running at `http://localhost:8000`.
***
-## Logging Configurations
-
-### Log Level
-
-The available log levels, from lowest to highest levels, are:
-
-1. `silly` (default, meaning messages of all levels are logged)
-2. `debug`
-3. `info`
-4. `warn`
-5. `error`
-
-You can change that by setting the `LOG_LEVEL` environment variable to the minimum level you want to be logged.
-
-For example:
-
-```bash
-LOG_LEVEL=error
-```
-
-This logs `error` messages only.
-
-The environment variable must be set as a system environment variable and not in `.env`.
-
-### Save Logs in a File
-
-Aside from showing the logs in the terminal, you can save the logs in a file by setting the `LOG_FILE` environment variable to the path of the file relative to the Medusa server’s root directory.
-
-For example:
+## Customize Storefront
-```bash
-LOG_FILE=all.log
-```
+To customize the storefront, refer to the following directories:
-Your logs are now saved in the `all.log` file at the root of your Medusa application.
+- `src/app`: The storefront’s pages.
+- `src/modules`: The storefront’s components.
+- `src/styles`: The storefront’s styles.
-The environment variable must be set as a system environment variable and not in `.env`.
+You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started).
***
-## Show Log with Progress
-
-The `Logger` class has an `activity` method used to log a message of level `info`. If the Medusa application is running in a development environment, a spinner starts to show the activity's progress.
-
-For example:
-
-```ts title="src/jobs/log-message.ts"
-import { MedusaContainer } from "@medusajs/framework/types"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-
- const activityId = logger.activity("First log message")
-
- logger.progress(activityId, `Second log message`)
-
- logger.success(activityId, "Last log message")
-}
-```
-
-The `activity` method returns the ID of the started activity. This ID can then be passed to one of the following methods of the `Logger` class:
+## Configurations and Integrations
-- `progress`: Log a message of level `info` that indicates progress within that same activity.
-- `success`: Log a message of level `info` that indicates that the activity has succeeded. This also ends the associated activity.
-- `failure`: Log a message of level `error` that indicates that the activity has failed. This also ends the associated activity.
+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.
-If you configured the `LOG_LEVEL` environment variable to a level higher than those associated with the above methods, their messages won’t be logged.
+Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
# Create Brands UI Route in Admin
@@ -3814,352 +3814,276 @@ You can also run the `npx medusa db:sync-links` to just sync module links withou
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: Extend Create Product Flow
+# Guide: Sync Brands from Medusa to CMS
-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.
+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.
-Some API routes, including the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) of the workflow executed in the API route, allowing you to consume those hooks and perform actions with the custom data.
+In another previous chapter, you [added a workflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md) that creates a brand. After integrating the CMS, you want to sync that brand to the third-party system as well.
-So, in this chapter, to extend the create product flow and associate a brand with a product, you will:
+Medusa has an event system that emits events when an operation is performed. It allows you to listen to those events and perform an asynchronous action in a function called a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md). This is useful to perform actions that aren't integral to the original flow, such as syncing data to a third-party system.
-- Consume the [productsCreated](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), which is executed within the workflow after the product is created. You'll link the product with the brand passed in the `additional_data` parameter.
-- Extend the Create Product API route to allow passing a brand ID in `additional_data`.
+Learn more about Medusa's event system and subscribers in [this chapter](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
-To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
+In this chapter, you'll modify the `createBrandWorkflow` you created before to emit a custom event that indicates a brand was created. Then, you'll listen to that event in a subscriber to sync the brand to the third-party CMS. You'll implement the sync logic within a workflow that you execute in the subscriber.
### 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)
-
-***
-
-## 1. Consume the productsCreated Hook
-
-A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. Consuming a workflow hook allows you to extend the features of a workflow and, consequently, the API route that uses it.
+- [createBrandWorkflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md)
+- [CMS Module](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md)
-Learn more about the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
+## 1. Emit Event in createBrandWorkflow
-The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) has a `productsCreated` hook that runs after the product is created. You'll consume this hook to link the created product with the brand specified in the request parameters.
+Since syncing the brand to the third-party system isn't integral to creating a brand, you'll emit a custom event indicating that a brand was created.
-To consume the `productsCreated` hook, create the file `src/workflows/hooks/created-product.ts` with the following content:
+Medusa provides an `emitEventStep` that allows you to emit an event in your workflows. So, in the `createBrandWorkflow` defined in `src/workflows/create-brand.ts`, use the `emitEventStep` helper step after the `createBrandStep`:
-
+```ts title="src/workflows/create-brand.ts" highlights={eventHighlights}
+// other imports...
+import {
+ emitEventStep,
+} from "@medusajs/medusa/core-flows"
-```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights}
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
-import { StepResponse } from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-import { LinkDefinition } from "@medusajs/framework/types"
-import { BRAND_MODULE } from "../../modules/brand"
-import BrandModuleService from "../../modules/brand/service"
+// ...
-createProductsWorkflow.hooks.productsCreated(
- (async ({ products, additional_data }, { container }) => {
- if (!additional_data?.brand_id) {
- return new StepResponse([], [])
- }
+export const createBrandWorkflow = createWorkflow(
+ "create-brand",
+ (input: CreateBrandInput) => {
+ // ...
- const brandModuleService: BrandModuleService = container.resolve(
- BRAND_MODULE
- )
- // if the brand doesn't exist, an error is thrown.
- await brandModuleService.retrieveBrand(additional_data.brand_id as string)
+ emitEventStep({
+ eventName: "brand.created",
+ data: {
+ id: brand.id,
+ },
+ })
- // TODO link brand to product
- })
+ return new WorkflowResponse(brand)
+ }
)
```
-Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productsCreated`, accepts a step function as a parameter. The step function accepts the following parameters:
-
-1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products.
-2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to resolve framework and commerce tools.
-
-In the step, if a brand ID is passed in `additional_data`, you resolve the Brand Module's service and use its generated `retrieveBrand` method to retrieve the brand by its ID. The `retrieveBrand` method will throw an error if the brand doesn't exist.
-
-### Link Brand to Product
+The `emitEventStep` accepts an object parameter having two properties:
-Next, you want to create a link between the created products and the brand. To do so, you use Link, which is a class from the Modules SDK that provides methods to manage linked records.
+- `eventName`: The name of the event to emit. You'll use this name later to listen to the event in a subscriber.
+- `data`: The data payload to emit with the event. This data is passed to subscribers that listen to the event. You add the brand's ID to the data payload, informing the subscribers which brand was created.
-Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
+You'll learn how to handle this event in a later step.
-To use Link in the `productsCreated` hook, replace the `TODO` with the following:
+***
-```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights}
-const link = container.resolve("link")
-const logger = container.resolve("logger")
+## 2. Create Sync to Third-Party System Workflow
-const links: LinkDefinition[] = []
+The subscriber that will listen to the `brand.created` event will sync the created brand to the third-party CMS. So, you'll implement the syncing logic in a workflow, then execute the workflow in the subscriber.
-for (const product of products) {
- links.push({
- [Modules.PRODUCT]: {
- product_id: product.id,
- },
- [BRAND_MODULE]: {
- brand_id: additional_data.brand_id,
- },
- })
-}
+Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution.
-await link.create(links)
+Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
-logger.info("Linked brand to products")
+You'll create a `syncBrandToSystemWorkflow` that has two steps:
-return new StepResponse(links, links)
-```
+- `useQueryGraphStep`: a step that Medusa provides to retrieve data using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). You'll use this to retrieve the brand's details using its ID.
+- `syncBrandToCmsStep`: a step that you'll create to sync the brand to the CMS.
-You resolve Link from the container. Then you loop over the created products to assemble an array of links to be created. After that, you pass the array of links to Link's `create` method, which will link the product and brand records.
+### syncBrandToCmsStep
-Each property in the link object is the name of a module, and its value is an object having a `{model_name}_id` property, where `{model_name}` is the snake-case name of the module's data model. Its value is the ID of the record to be linked. The link object's properties must be set in the same order as the link configurations passed to `defineLink`.
+To implement the step that syncs the brand to the CMS, create the file `src/workflows/sync-brands-to-cms.ts` with the following content:
-
+
-Finally, you return an instance of `StepResponse` returning the created links.
+```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncStepHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { InferTypeOf } from "@medusajs/framework/types"
+import { Brand } from "../modules/brand/models/brand"
+import { CMS_MODULE } from "../modules/cms"
+import CmsModuleService from "../modules/cms/service"
-### Dismiss Links in Compensation
+type SyncBrandToCmsStepInput = {
+ brand: InferTypeOf
+}
-You can pass as a second parameter of the hook a compensation function that undoes what the step did. It receives as a first parameter the returned `StepResponse`'s second parameter, and the step context object as a second parameter.
+const syncBrandToCmsStep = createStep(
+ "sync-brand-to-cms",
+ async ({ brand }: SyncBrandToCmsStepInput, { container }) => {
+ const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)
-To undo creating the links in the hook, pass the following compensation function as a second parameter to `productsCreated`:
+ await cmsModuleService.createBrand(brand)
-```ts title="src/workflows/hooks/created-product.ts"
-createProductsWorkflow.hooks.productsCreated(
- // ...
- (async (links, { container }) => {
- if (!links?.length) {
+ return new StepResponse(null, brand.id)
+ },
+ async (id, { container }) => {
+ if (!id) {
return
}
- const link = container.resolve("link")
+ const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)
- await link.dismiss(links)
- })
+ await cmsModuleService.deleteBrand(id)
+ }
)
```
-In the compensation function, if the `links` parameter isn't empty, you resolve Link from the container and use its `dismiss` method. This method removes a link between two records. It accepts the same parameter as the `create` method.
-
-***
+You create the `syncBrandToCmsStep` that accepts a brand as an input. In the step, you resolve the CMS Module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) and use its `createBrand` method. This method will create the brand in the third-party CMS.
-## 2. Configure Additional Data Validation
+You also pass the brand's ID to the step's compensation function. In this function, you delete the brand in the third-party CMS if an error occurs during the workflow's execution.
-Now that you've consumed the `productsCreated` hook, you want to configure the `/admin/products` API route that creates a new product to accept a brand ID in its `additional_data` parameter.
+Learn more about compensation functions in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
-You configure the properties accepted in `additional_data` in the `src/api/middlewares.ts` that exports middleware configurations. So, create the file (or, if already existing, add to the file) `src/api/middlewares.ts` the following content:
+### Create Workflow
-
+You can now create the workflow that uses the above step. Add the workflow to the same `src/workflows/sync-brands-to-cms.ts` file:
-```ts title="src/api/middlewares.ts"
-import { defineMiddlewares } from "@medusajs/framework/http"
-import { z } from "zod"
+```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncWorkflowHighlights}
+// other imports...
+import {
+ // ...
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-export default defineMiddlewares({
- routes: [
- // ...
- {
- matcher: "/admin/products",
- method: ["POST"],
- additionalDataValidator: {
- brand_id: z.string().optional(),
- },
- },
- ],
-})
-```
-
-Objects in `routes` accept an `additionalDataValidator` property that configures the validation rules for custom properties passed in the `additional_data` request parameter. It accepts an object whose keys are custom property names, and their values are validation rules created using [Zod](https://zod.dev/).
-
-So, `POST` requests sent to `/admin/products` can now pass the ID of a brand in the `brand_id` property of `additional_data`.
-
-***
+type SyncBrandToCmsWorkflowInput = {
+ id: string
+}
-## Test it Out
+export const syncBrandToCmsWorkflow = createWorkflow(
+ "sync-brand-to-cms",
+ (input: SyncBrandToCmsWorkflowInput) => {
+ // @ts-ignore
+ const { data: brands } = useQueryGraphStep({
+ entity: "brand",
+ fields: ["*"],
+ filters: {
+ id: input.id,
+ },
+ options: {
+ throwIfKeyNotFound: true,
+ },
+ })
-To test it out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`:
+ syncBrandToCmsStep({
+ brand: brands[0],
+ } as SyncBrandToCmsStepInput)
-```bash
-curl -X POST 'http://localhost:9000/auth/user/emailpass' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "admin@medusa-test.com",
- "password": "supersecret"
-}'
+ return new WorkflowResponse({})
+ }
+)
```
-Make sure to replace the email and password in the request body with your user's credentials.
-
-Then, send a `POST` request to `/admin/products` to create a product, and pass in the `additional_data` parameter a brand's ID:
-
-```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_id": "{brand_id}"
- }
-}'
-```
+You create a `syncBrandToCmsWorkflow` that accepts the brand's ID as input. The workflow has the following steps:
-Make sure to replace `{token}` with the token you received from the previous request, `shipping_profile_id` with the ID of a shipping profile in your application, and `{brand_id}` with the ID of a brand in your application. You can retrieve the ID of a shipping profile either from the Medusa Admin, or the [List Shipping Profiles API route](https://docs.medusajs.com/api/admin#shipping-profiles_getshippingprofiles).
+- `useQueryGraphStep`: Retrieve the brand's details using Query. You pass the brand's ID as a filter, and set the `throwIfKeyNotFound` option to true so that the step throws an error if a brand with the specified ID doesn't exist.
+- `syncBrandToCmsStep`: Create the brand in the third-party CMS.
-The request creates a product and returns it.
+You'll execute this workflow in the subscriber next.
-In the Medusa application's logs, you'll find the message `Linked brand to products`, indicating that the workflow hook handler ran and linked the brand to the products.
+Learn more about `useQueryGraphStep` in [this reference](https://docs.medusajs.com/resources/references/helper-steps/useQueryGraphStep/index.html.md).
***
-## Next Steps: Query Linked Brands and Products
-
-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: 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)
+## 3. Handle brand.created Event
-***
+You now have a workflow with the logic to sync a brand to the CMS. You need to execute this workflow whenever the `brand.created` event is emitted. So, you'll create a subscriber that listens to and handle the event.
-## Approach 1: Retrieve Brands in Existing API Routes
+Subscribers are created in a TypeScript or JavaScript file under the `src/subscribers` directory. So, create the file `src/subscribers/brand-created.ts` with the following content:
-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).
+```ts title="src/subscribers/brand-created.ts" highlights={subscriberHighlights}
+import type {
+ SubscriberConfig,
+ SubscriberArgs,
+} from "@medusajs/framework"
+import { syncBrandToCmsWorkflow } from "../workflows/sync-brands-to-cms"
-For example, send the following request to retrieve the list of products with their brands:
+export default async function brandCreatedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ await syncBrandToCmsWorkflow(container).run({
+ input: data,
+ })
+}
-```bash
-curl 'http://localhost:9000/admin/products?fields=+brand.*' \
---header 'Authorization: Bearer {token}'
+export const config: SubscriberConfig = {
+ event: "brand.created",
+}
```
-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:
+A subscriber file must export:
-```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
- }
-}
-```
+- The asynchronous function that's executed when the event is emitted. This must be the file's default export.
+- An object that holds the subscriber's configurations. It has an `event` property that indicates the name of the event that the subscriber is listening to.
-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.
+The subscriber function accepts an object parameter that has two properties:
-***
+- `event`: An object of event details. Its `data` property holds the event's data payload, which is the brand's ID.
+- `container`: The Medusa container used to resolve framework and commerce tools.
-## Approach 2: Use Query to Retrieve Linked Records
+In the function, you execute the `syncBrandToCmsWorkflow`, passing it the data payload as an input. So, everytime a brand is created, Medusa will execute this function, which in turn executes the workflow to sync the brand to the CMS.
-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 subscribers in [this chapter](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
-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:
+## Test it Out
-```ts title="src/api/admin/brands/route.ts" highlights={highlights}
-// other imports...
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
+To test the subscriber and workflow out, you'll use the [Create Brand API route](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md) you created in a previous chapter.
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve("query")
-
- const { data: brands } = await query.graph({
- entity: "brand",
- fields: ["*", "products.*"],
- })
+First, start the Medusa application:
- res.json({ brands })
-}
+```bash npm2yarn
+npm run dev
```
-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:
+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:
-- `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.
+```bash
+curl -X POST 'http://localhost:9000/auth/user/emailpass' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "admin@medusa-test.com",
+ "password": "supersecret"
+}'
+```
-`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
+Make sure to replace the email and password with your admin user's credentials.
-### Test it Out
+Don't have an admin user? Refer to [this guide](https://docs.medusajs.com/learn/installation#create-medusa-admin-user/index.html.md).
-To test the API route out, send a `GET` request to `/admin/brands`:
+Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header:
```bash
-curl 'http://localhost:9000/admin/brands' \
--H 'Authorization: Bearer {token}'
+curl -X POST 'http://localhost:9000/admin/brands' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "name": "Acme"
+}'
```
-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:
+This request returns the created brand. If you check the logs, you'll find the `brand.created` event was emitted, and that the request to the third-party system was simulated:
-```json title="Example Response"
-{
- "brands": [
- {
- "id": "123",
- // ...
- "products": [
- {
- "id": "prod_123",
- // ...
- }
- ]
- }
- ]
+```plain
+info: Processing brand.created which has 1 subscribers
+http: POST /admin/brands ← - (200) - 16.418 ms
+info: Sending a POST request to /brands.
+info: Request Data: {
+ "id": "01JEDWENYD361P664WRQPMC3J8",
+ "name": "Acme",
+ "created_at": "2024-12-06T11:42:32.909Z",
+ "updated_at": "2024-12-06T11:42:32.909Z",
+ "deleted_at": null
}
+info: API Key: "123"
```
***
-## 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.
+## Next Chapter: Sync Brand from Third-Party CMS to Medusa
-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.
+You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day.
# Guide: Schedule Syncing Brands from CMS
@@ -4471,389 +4395,308 @@ 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 CMS 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.
+# Guide: Query Product's Brands
-Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+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.
-## 1. Create Module Directory
+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.
-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.
+### 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)
***
-## 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:
+## 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.
-```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
-import { Logger, ConfigModule } from "@medusajs/framework/types"
+Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
-export type ModuleOptions = {
- apiKey: string
-}
+For example, send the following request to retrieve the list of products with their brands:
-type InjectedDependencies = {
- logger: Logger
- configModule: ConfigModule
-}
+```bash
+curl 'http://localhost:9000/admin/products?fields=+brand.*' \
+--header 'Authorization: Bearer {token}'
+```
-class CmsModuleService {
- private options_: ModuleOptions
- private logger_: Logger
+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).
- constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
- this.logger_ = logger
- this.options_ = options
+Any product that is linked to a brand will have a `brand` property in its object:
- // TODO initialize SDK
+```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
}
}
-
-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.
+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.
-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
+## Approach 2: Use Query to Retrieve Linked Records
-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.
+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.
-Add the following methods in the `CmsModuleService`:
+Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
-export class CmsModuleService {
- // ...
+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:
- // 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)}`)
- }
+```ts title="src/api/admin/brands/route.ts" highlights={highlights}
+// other imports...
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
- 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")
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve("query")
+
+ const { data: brands } = await query.graph({
+ entity: "brand",
+ fields: ["*", "products.*"],
+ })
- return []
- }
+ res.json({ brands })
}
```
-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.
+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:
-Create the file `src/modules/cms/index.ts` with the following content:
+- `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.
-```ts title="src/modules/cms/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import CmsModuleService from "./service"
+### Test it Out
-export const CMS_MODULE = "cms"
+To test the API route out, send a `GET` request to `/admin/brands`:
-export default Module(CMS_MODULE, {
- service: CmsModuleService,
-})
+```bash
+curl 'http://localhost:9000/admin/brands' \
+-H 'Authorization: Bearer {token}'
```
-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
+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).
-Finally, add the module to the Medusa configurations at `medusa-config.ts`:
+This returns the brands in your store with their linked products. For example:
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- // ...
+```json title="Example Response"
+{
+ "brands": [
{
- resolve: "./src/modules/cms",
- options: {
- apiKey: process.env.CMS_API_KEY,
- },
- },
- ],
-})
+ "id": "123",
+ // ...
+ "products": [
+ {
+ "id": "prod_123",
+ // ...
+ }
+ ]
+ }
+ ]
+}
```
-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`:
+## Summary
-```bash
-CMS_API_KEY=123
-```
+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: Sync Brand From Medusa to CMS
+## Next Steps: Customize Medusa Admin
-You can now use the CMS Module's service to perform actions on the third-party CMS.
+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 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.
+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 CMS
+# Guide: Extend Create Product Flow
-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.
+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.
-In another previous chapter, you [added a workflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md) that creates a brand. After integrating the CMS, you want to sync that brand to the third-party system as well.
+Some API routes, including the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) of the workflow executed in the API route, allowing you to consume those hooks and perform actions with the custom data.
-Medusa has an event system that emits events when an operation is performed. It allows you to listen to those events and perform an asynchronous action in a function called a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md). This is useful to perform actions that aren't integral to the original flow, such as syncing data to a third-party system.
+So, in this chapter, to extend the create product flow and associate a brand with a product, you will:
-Learn more about Medusa's event system and subscribers in [this chapter](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
+- Consume the [productsCreated](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), which is executed within the workflow after the product is created. You'll link the product with the brand passed in the `additional_data` parameter.
+- Extend the Create Product API route to allow passing a brand ID in `additional_data`.
-In this chapter, you'll modify the `createBrandWorkflow` you created before to emit a custom event that indicates a brand was created. Then, you'll listen to that event in a subscriber to sync the brand to the third-party CMS. You'll implement the sync logic within a workflow that you execute in the subscriber.
+To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
### Prerequisites
-- [createBrandWorkflow](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md)
-- [CMS Module](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md)
-
-## 1. Emit Event in createBrandWorkflow
-
-Since syncing the brand to the third-party system isn't integral to creating a brand, you'll emit a custom event indicating that a brand was created.
+- [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)
-Medusa provides an `emitEventStep` that allows you to emit an event in your workflows. So, in the `createBrandWorkflow` defined in `src/workflows/create-brand.ts`, use the `emitEventStep` helper step after the `createBrandStep`:
+***
-```ts title="src/workflows/create-brand.ts" highlights={eventHighlights}
-// other imports...
-import {
- emitEventStep,
-} from "@medusajs/medusa/core-flows"
+## 1. Consume the productsCreated Hook
-// ...
+A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. Consuming a workflow hook allows you to extend the features of a workflow and, consequently, the API route that uses it.
-export const createBrandWorkflow = createWorkflow(
- "create-brand",
- (input: CreateBrandInput) => {
- // ...
+Learn more about the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md).
- emitEventStep({
- eventName: "brand.created",
- data: {
- id: brand.id,
- },
- })
+The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) has a `productsCreated` hook that runs after the product is created. You'll consume this hook to link the created product with the brand specified in the request parameters.
- return new WorkflowResponse(brand)
- }
-)
-```
+To consume the `productsCreated` hook, create the file `src/workflows/hooks/created-product.ts` with the following content:
-The `emitEventStep` accepts an object parameter having two properties:
+
-- `eventName`: The name of the event to emit. You'll use this name later to listen to the event in a subscriber.
-- `data`: The data payload to emit with the event. This data is passed to subscribers that listen to the event. You add the brand's ID to the data payload, informing the subscribers which brand was created.
+```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights}
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+import { StepResponse } from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+import { LinkDefinition } from "@medusajs/framework/types"
+import { BRAND_MODULE } from "../../modules/brand"
+import BrandModuleService from "../../modules/brand/service"
-You'll learn how to handle this event in a later step.
+createProductsWorkflow.hooks.productsCreated(
+ (async ({ products, additional_data }, { container }) => {
+ if (!additional_data?.brand_id) {
+ return new StepResponse([], [])
+ }
-***
+ const brandModuleService: BrandModuleService = container.resolve(
+ BRAND_MODULE
+ )
+ // if the brand doesn't exist, an error is thrown.
+ await brandModuleService.retrieveBrand(additional_data.brand_id as string)
-## 2. Create Sync to Third-Party System Workflow
+ // TODO link brand to product
+ })
+)
+```
-The subscriber that will listen to the `brand.created` event will sync the created brand to the third-party CMS. So, you'll implement the syncing logic in a workflow, then execute the workflow in the subscriber.
+Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productsCreated`, accepts a step function as a parameter. The step function accepts the following parameters:
-Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution.
+1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products.
+2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to resolve framework and commerce tools.
-Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md).
+In the step, if a brand ID is passed in `additional_data`, you resolve the Brand Module's service and use its generated `retrieveBrand` method to retrieve the brand by its ID. The `retrieveBrand` method will throw an error if the brand doesn't exist.
-You'll create a `syncBrandToSystemWorkflow` that has two steps:
+### Link Brand to Product
-- `useQueryGraphStep`: a step that Medusa provides to retrieve data using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). You'll use this to retrieve the brand's details using its ID.
-- `syncBrandToCmsStep`: a step that you'll create to sync the brand to the CMS.
+Next, you want to create a link between the created products and the brand. To do so, you use Link, which is a class from the Modules SDK that provides methods to manage linked records.
-### syncBrandToCmsStep
+Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md).
-To implement the step that syncs the brand to the CMS, create the file `src/workflows/sync-brands-to-cms.ts` with the following content:
+To use Link in the `productsCreated` hook, replace the `TODO` with the following:
-
+```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights}
+const link = container.resolve("link")
+const logger = container.resolve("logger")
-```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncStepHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-import { InferTypeOf } from "@medusajs/framework/types"
-import { Brand } from "../modules/brand/models/brand"
-import { CMS_MODULE } from "../modules/cms"
-import CmsModuleService from "../modules/cms/service"
+const links: LinkDefinition[] = []
-type SyncBrandToCmsStepInput = {
- brand: InferTypeOf
+for (const product of products) {
+ links.push({
+ [Modules.PRODUCT]: {
+ product_id: product.id,
+ },
+ [BRAND_MODULE]: {
+ brand_id: additional_data.brand_id,
+ },
+ })
}
-const syncBrandToCmsStep = createStep(
- "sync-brand-to-cms",
- async ({ brand }: SyncBrandToCmsStepInput, { container }) => {
- const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)
-
- await cmsModuleService.createBrand(brand)
-
- return new StepResponse(null, brand.id)
- },
- async (id, { container }) => {
- if (!id) {
- return
- }
+await link.create(links)
- const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)
+logger.info("Linked brand to products")
- await cmsModuleService.deleteBrand(id)
- }
-)
+return new StepResponse(links, links)
```
-You create the `syncBrandToCmsStep` that accepts a brand as an input. In the step, you resolve the CMS Module's service from the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) and use its `createBrand` method. This method will create the brand in the third-party CMS.
-
-You also pass the brand's ID to the step's compensation function. In this function, you delete the brand in the third-party CMS if an error occurs during the workflow's execution.
+You resolve Link from the container. Then you loop over the created products to assemble an array of links to be created. After that, you pass the array of links to Link's `create` method, which will link the product and brand records.
-Learn more about compensation functions in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/compensation-function/index.html.md).
+Each property in the link object is the name of a module, and its value is an object having a `{model_name}_id` property, where `{model_name}` is the snake-case name of the module's data model. Its value is the ID of the record to be linked. The link object's properties must be set in the same order as the link configurations passed to `defineLink`.
-### Create Workflow
+
-You can now create the workflow that uses the above step. Add the workflow to the same `src/workflows/sync-brands-to-cms.ts` file:
+Finally, you return an instance of `StepResponse` returning the created links.
-```ts title="src/workflows/sync-brands-to-cms.ts" highlights={syncWorkflowHighlights}
-// other imports...
-import {
- // ...
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+### Dismiss Links in Compensation
-// ...
+You can pass as a second parameter of the hook a compensation function that undoes what the step did. It receives as a first parameter the returned `StepResponse`'s second parameter, and the step context object as a second parameter.
-type SyncBrandToCmsWorkflowInput = {
- id: string
-}
+To undo creating the links in the hook, pass the following compensation function as a second parameter to `productsCreated`:
-export const syncBrandToCmsWorkflow = createWorkflow(
- "sync-brand-to-cms",
- (input: SyncBrandToCmsWorkflowInput) => {
- // @ts-ignore
- const { data: brands } = useQueryGraphStep({
- entity: "brand",
- fields: ["*"],
- filters: {
- id: input.id,
- },
- options: {
- throwIfKeyNotFound: true,
- },
- })
+```ts title="src/workflows/hooks/created-product.ts"
+createProductsWorkflow.hooks.productsCreated(
+ // ...
+ (async (links, { container }) => {
+ if (!links?.length) {
+ return
+ }
- syncBrandToCmsStep({
- brand: brands[0],
- } as SyncBrandToCmsStepInput)
+ const link = container.resolve("link")
- return new WorkflowResponse({})
- }
+ await link.dismiss(links)
+ })
)
```
-You create a `syncBrandToCmsWorkflow` that accepts the brand's ID as input. The workflow has the following steps:
-
-- `useQueryGraphStep`: Retrieve the brand's details using Query. You pass the brand's ID as a filter, and set the `throwIfKeyNotFound` option to true so that the step throws an error if a brand with the specified ID doesn't exist.
-- `syncBrandToCmsStep`: Create the brand in the third-party CMS.
-
-You'll execute this workflow in the subscriber next.
-
-Learn more about `useQueryGraphStep` in [this reference](https://docs.medusajs.com/resources/references/helper-steps/useQueryGraphStep/index.html.md).
+In the compensation function, if the `links` parameter isn't empty, you resolve Link from the container and use its `dismiss` method. This method removes a link between two records. It accepts the same parameter as the `create` method.
***
-## 3. Handle brand.created Event
+## 2. Configure Additional Data Validation
-You now have a workflow with the logic to sync a brand to the CMS. You need to execute this workflow whenever the `brand.created` event is emitted. So, you'll create a subscriber that listens to and handle the event.
+Now that you've consumed the `productsCreated` hook, you want to configure the `/admin/products` API route that creates a new product to accept a brand ID in its `additional_data` parameter.
-Subscribers are created in a TypeScript or JavaScript file under the `src/subscribers` directory. So, create the file `src/subscribers/brand-created.ts` with the following content:
+You configure the properties accepted in `additional_data` in the `src/api/middlewares.ts` that exports middleware configurations. So, create the file (or, if already existing, add to the file) `src/api/middlewares.ts` the following content:
-
+
-```ts title="src/subscribers/brand-created.ts" highlights={subscriberHighlights}
-import type {
- SubscriberConfig,
- SubscriberArgs,
-} from "@medusajs/framework"
-import { syncBrandToCmsWorkflow } from "../workflows/sync-brands-to-cms"
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/framework/http"
+import { z } from "zod"
-export default async function brandCreatedHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- await syncBrandToCmsWorkflow(container).run({
- input: data,
- })
-}
+// ...
-export const config: SubscriberConfig = {
- event: "brand.created",
-}
+export default defineMiddlewares({
+ routes: [
+ // ...
+ {
+ matcher: "/admin/products",
+ method: ["POST"],
+ additionalDataValidator: {
+ brand_id: z.string().optional(),
+ },
+ },
+ ],
+})
```
-A subscriber file must export:
-
-- The asynchronous function that's executed when the event is emitted. This must be the file's default export.
-- An object that holds the subscriber's configurations. It has an `event` property that indicates the name of the event that the subscriber is listening to.
-
-The subscriber function accepts an object parameter that has two properties:
-
-- `event`: An object of event details. Its `data` property holds the event's data payload, which is the brand's ID.
-- `container`: The Medusa container used to resolve framework and commerce tools.
-
-In the function, you execute the `syncBrandToCmsWorkflow`, passing it the data payload as an input. So, everytime a brand is created, Medusa will execute this function, which in turn executes the workflow to sync the brand to the CMS.
+Objects in `routes` accept an `additionalDataValidator` property that configures the validation rules for custom properties passed in the `additional_data` request parameter. It accepts an object whose keys are custom property names, and their values are validation rules created using [Zod](https://zod.dev/).
-Learn more about subscribers in [this chapter](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
+So, `POST` requests sent to `/admin/products` can now pass the ID of a brand in the `brand_id` property of `additional_data`.
***
## Test it Out
-To test the subscriber and workflow out, you'll use the [Create Brand API route](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md) you created in a previous chapter.
-
-First, start the Medusa application:
-
-```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:
+To test it out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`:
```bash
curl -X POST 'http://localhost:9000/auth/user/emailpass' \
@@ -4864,42 +4707,40 @@ curl -X POST 'http://localhost:9000/auth/user/emailpass' \
}'
```
-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).
+Make sure to replace the email and password in the request body with your user's credentials.
-Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header:
+Then, send a `POST` request to `/admin/products` to create a product, and pass in the `additional_data` parameter a brand's ID:
```bash
-curl -X POST 'http://localhost:9000/admin/brands' \
+curl -X POST 'http://localhost:9000/admin/products' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {token}' \
--data '{
- "name": "Acme"
+ "title": "Product 1",
+ "options": [
+ {
+ "title": "Default option",
+ "values": ["Default option value"]
+ }
+ ],
+ "shipping_profile_id": "{shipping_profile_id}",
+ "additional_data": {
+ "brand_id": "{brand_id}"
+ }
}'
```
-This request returns the created brand. If you check the logs, you'll find the `brand.created` event was emitted, and that the request to the third-party system was simulated:
+Make sure to replace `{token}` with the token you received from the previous request, `shipping_profile_id` with the ID of a shipping profile in your application, and `{brand_id}` with the ID of a brand in your application. You can retrieve the ID of a shipping profile either from the Medusa Admin, or the [List Shipping Profiles API route](https://docs.medusajs.com/api/admin#shipping-profiles_getshippingprofiles).
-```plain
-info: Processing brand.created which has 1 subscribers
-http: POST /admin/brands ← - (200) - 16.418 ms
-info: Sending a POST request to /brands.
-info: Request Data: {
- "id": "01JEDWENYD361P664WRQPMC3J8",
- "name": "Acme",
- "created_at": "2024-12-06T11:42:32.909Z",
- "updated_at": "2024-12-06T11:42:32.909Z",
- "deleted_at": null
-}
-info: API Key: "123"
-```
+The request creates a product and returns it.
+
+In the Medusa application's logs, you'll find the message `Linked brand to products`, indicating that the workflow hook handler ran and linked the brand to the products.
***
-## Next Chapter: Sync Brand from Third-Party CMS to Medusa
+## Next Steps: Query Linked Brands and Products
-You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day.
+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.
# Admin Development Constraints
@@ -4947,258 +4788,261 @@ export const config = defineWidgetConfig({
```
-# 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.
-
-In this chapter, you'll learn about routing-related customizations that you can use in your admin customizations using React Router.
-
-`react-router-dom` is available in your project by default through the Medusa packages. You don't need to install it separately.
-
-## Link to a Page
-
-To link to a page in your admin customizations, you can use the `Link` component from `react-router-dom`. For example:
+# Guide: Integrate CMS Brand System
-```tsx title="src/admin/widgets/product-widget.tsx" highlights={highlights}
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "@medusajs/ui"
-import { Link } from "react-router-dom"
+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.
-// The widget
-const ProductWidget = () => {
- return (
-
- View Orders
-
- )
-}
+Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-// The widget's configurations
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
+## 1. Create Module Directory
-export default ProductWidget
-```
+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.
-This adds a widget to a product's details page with a link to the Orders page. The link's path must be without the `/app` prefix.
+
***
-## Admin Route Loader
-
-Route loaders are available starting from Medusa v2.5.1.
+## 2. Create Module Service
-In your UI route or any other custom admin route, you may need to retrieve data to use it in your route component. For example, you may want to fetch a list of products to display on a custom page.
+Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
-To do that, you can export a `loader` function in the route file, which is a [React Router loader](https://reactrouter.com/6.29.0/route/loader#loader). In this function, you can fetch and return data asynchronously. Then, in your route component, you can use the [useLoaderData](https://reactrouter.com/6.29.0/hooks/use-loader-data#useloaderdata) hook from React Router to access the data.
+Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
-For example, consider the following UI route created at `src/admin/routes/custom/page.tsx`:
+
-```tsx title="src/admin/routes/custom/page.tsx" highlights={loaderHighlights}
-import { Container, Heading } from "@medusajs/ui"
-import {
- useLoaderData,
-} from "react-router-dom"
+```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
+import { Logger, ConfigModule } from "@medusajs/framework/types"
-export async function loader() {
- // TODO fetch products
+export type ModuleOptions = {
+ apiKey: string
+}
- return {
- products: [],
- }
+type InjectedDependencies = {
+ logger: Logger
+ configModule: ConfigModule
}
-const CustomPage = () => {
- const { products } = useLoaderData() as Awaited>
+class CmsModuleService {
+ private options_: ModuleOptions
+ private logger_: Logger
- return (
-
-
-
- Products count: {products.length}
-
-
-
- )
+ constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
+ this.logger_ = logger
+ this.options_ = options
+
+ // TODO initialize SDK
+ }
}
-export default CustomPage
+export default CmsModuleService
```
-In this example, you first export a `loader` function that can be used to fetch data, such as products. The function returns an object with a `products` property.
+You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
-Then, in the `CustomPage` route component, you use the `useLoaderData` hook from React Router to access the data returned by the `loader` function. You can then use the data in your component.
+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.
-### Route Parameters
+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.
-You can also access route params in the loader function. For example, consider the following UI route created at `src/admin/routes/custom/[id]/page.tsx`:
+### Integration Methods
-```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={loaderParamHighlights}
-import { Container, Heading } from "@medusajs/ui"
-import {
- useLoaderData,
- LoaderFunctionArgs,
-} from "react-router-dom"
+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.
-export async function loader({ params }: LoaderFunctionArgs) {
- const { id } = params
- // TODO fetch product by id
+Add the following methods in the `CmsModuleService`:
- return {
- id,
+```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)}`)
}
-}
-const CustomPage = () => {
- const { id } = useLoaderData() as Awaited>
+ async createBrand(brand: Record) {
+ await this.sendRequest("/brands", "POST", brand)
+ }
- return (
-
-
-
- Product ID: {id}
-
-
-
- )
-}
+ async deleteBrand(id: string) {
+ await this.sendRequest(`/brands/${id}`, "DELETE")
+ }
-export default CustomPage
+ async retrieveBrands(): Promise[]> {
+ await this.sendRequest("/brands", "GET")
+
+ return []
+ }
+}
```
-Because the UI route has a route parameter `[id]`, you can access the `id` parameter in the `loader` function. The loader function accepts as a parameter an object of type `LoaderFunctionArgs` from React Router. This object has a `params` property that contains the route parameters.
+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.
-In the loader, you can fetch data asynchronously using the route parameter and return it. Then, in the route component, you can access the data using the `useLoaderData` hook.
+You also add three methods that use the `sendRequest` method:
-### When to Use Route Loaders
+- `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.
-A route loader is executed before the route is loaded. So, it will block navigation until the loader function is resolved.
+***
-Only use route loaders when the route component needs data essential before rendering. Otherwise, use the JS SDK with Tanstack (React) Query as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md). This way, you can fetch data asynchronously and update the UI when the data is available. You can also use a loader to prepare some initial data that's used in the route component before the data is retrieved.
+## 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`.
***
-## Other React Router Utilities
+## 4. Add Module to Medusa's Configurations
-### Route Handles
+Finally, add the module to the Medusa configurations at `medusa-config.ts`:
-Route handles are available starting from Medusa v2.5.1.
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ // ...
+ {
+ resolve: "./src/modules/cms",
+ options: {
+ apiKey: process.env.CMS_API_KEY,
+ },
+ },
+ ],
+})
+```
-In your UI route or any route file, you can export a `handle` object to define [route handles](https://reactrouter.com/start/framework/route-module#handle). The object is passed to the loader and route contexts.
+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.
-For example:
+You can add the `CMS_API_KEY` environment variable to `.env`:
-```tsx title="src/admin/routes/custom/page.tsx"
-export const handle = {
- sandbox: true,
-}
+```bash
+CMS_API_KEY=123
```
-### React Router Components and Hooks
+***
-Refer to [react-router-dom’s documentation](https://reactrouter.com/en/6.29.0) for components and hooks that you can use in your admin customizations.
+## 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.
-# Admin Development Tips
+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.
-In this chapter, you'll find some tips for your admin development.
-## Send Requests to API Routes
+# Environment Variables in Admin Customizations
-To send a request to an API route in the Medusa Application, use Medusa's [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) with [Tanstack Query](https://tanstack.com/query/latest). Both of these tools are installed in your project by default.
+In this chapter, you'll learn how to use environment variables in your admin customizations.
-Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
+To learn how envirnment variables are generally loaded in Medusa based on your application's environment, check out [this chapter](https://docs.medusajs.com/learn/fundamentals/environment-variables/index.html.md).
-First, create the file `src/admin/lib/config.ts` to setup the SDK for use in your customizations:
+## How to Set Environment Variables
-```ts
-import Medusa from "@medusajs/js-sdk"
+The Medusa Admin is built on top of [Vite](https://vite.dev/). To set an environment variable that you want to use in a widget or UI route, prefix the environment variable with `VITE_`.
-export const sdk = new Medusa({
- baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
- debug: import.meta.env.DEV,
- auth: {
- type: "session",
- },
-})
+For example:
+
+```plain
+VITE_MY_API_KEY=sk_123
```
-Notice that you use `import.meta.env` to access environment variables in your customizations, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md).
+***
-Learn more about the JS SDK's configurations [this documentation](https://docs.medusajs.com/resources/js-sdk#js-sdk-configurations/index.html.md).
+## How to Use Environment Variables
-Then, use the configured SDK with the `useQuery` Tanstack Query hook to send `GET` requests, and `useMutation` hook to send `POST` or `DELETE` requests.
+To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object.
For example:
-### Query
-
-```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights}
+```tsx highlights={[["8"]]}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Button, Container } from "@medusajs/ui"
-import { useQuery } from "@tanstack/react-query"
-import { sdk } from "../lib/config"
-import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types"
+import { Container, Heading } from "@medusajs/ui"
const ProductWidget = () => {
- const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list(),
- queryKey: ["products"],
- })
-
return (
- {isLoading && Loading...}
- {data?.products && (
-
- {data.products.map((product) => (
-
{product.title}
- ))}
-
- )}
+
+ API Key: {import.meta.env.VITE_MY_API_KEY}
+
)
}
export const config = defineWidgetConfig({
- zone: "product.list.before",
+ zone: "product.details.before",
})
export default ProductWidget
```
-### Mutation
+In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`.
-```tsx title="src/admin/widgets/product-widget.ts" highlights={mutationHighlights}
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Button, Container } from "@medusajs/ui"
-import { useMutation } from "@tanstack/react-query"
-import { sdk } from "../lib/config"
-import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types"
+### Type Error on import.meta.env
-const ProductWidget = ({
- data: productData,
-}: DetailWidgetProps) => {
- const { mutateAsync } = useMutation({
- mutationFn: (payload: HttpTypes.AdminUpdateProduct) =>
- sdk.admin.product.update(productData.id, payload),
- onSuccess: () => alert("updated product"),
- })
+If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content:
- const handleUpdate = () => {
- mutateAsync({
- title: "New Product Title",
- })
- }
-
+```ts title="src/admin/vite-env.d.ts"
+///
+```
+
+This file tells TypeScript to recognize the `import.meta.env` object and enhances the types of your custom environment variables.
+
+***
+
+## Check Node Environment in Admin Customizations
+
+To check the current environment, Vite exposes two variables:
+
+- `import.meta.env.DEV`: Returns `true` if the current environment is development.
+- `import.meta.env.PROD`: Returns `true` if the current environment is production.
+
+Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode).
+
+
+# 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.
+
+In this chapter, you'll learn about routing-related customizations that you can use in your admin customizations using React Router.
+
+`react-router-dom` is available in your project by default through the Medusa packages. You don't need to install it separately.
+
+## Link to a Page
+
+To link to a page in your admin customizations, you can use the `Link` component from `react-router-dom`. For example:
+
+```tsx title="src/admin/widgets/product-widget.tsx" highlights={highlights}
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "@medusajs/ui"
+import { Link } from "react-router-dom"
+
+// The widget
+const ProductWidget = () => {
return (
-
+ View Orders
)
}
+// The widget's configurations
export const config = defineWidgetConfig({
zone: "product.details.before",
})
@@ -5206,69 +5050,165 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-You can also send requests to custom routes as explained in the [JS SDK reference](https://docs.medusajs.com/resources/js-sdk/index.html.md).
+This adds a widget to a product's details page with a link to the Orders page. The link's path must be without the `/app` prefix.
-### Use Route Loaders for Initial Data
+***
-You may need to retrieve data before your component is rendered, or you may need to pass some initial data to your component to be used while data is being fetched. In those cases, you can use a [route loader](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
+## Admin Route Loader
-***
+Route loaders are available starting from Medusa v2.5.1.
-## Global Variables in Admin Customizations
+In your UI route or any other custom admin route, you may need to retrieve data to use it in your route component. For example, you may want to fetch a list of products to display on a custom page.
-In your admin customizations, you can use the following global variables:
+To do that, you can export a `loader` function in the route file, which is a [React Router loader](https://reactrouter.com/6.29.0/route/loader#loader). In this function, you can fetch and return data asynchronously. Then, in your route component, you can use the [useLoaderData](https://reactrouter.com/6.29.0/hooks/use-loader-data#useloaderdata) hook from React Router to access the data.
-- `__BASE__`: The base path of the Medusa Admin, as set in the [admin.path](https://docs.medusajs.com/resources/references/medusa-config#path/index.html.md) configuration in `medusa-config.ts`.
-- `__BACKEND_URL__`: The URL to the Medusa backend, as set in the [admin.backendUrl](https://docs.medusajs.com/resources/references/medusa-config#backendurl/index.html.md) configuration in `medusa-config.ts`.
-- `__STOREFRONT_URL__`: The URL to the storefront, as set in the [admin.storefrontUrl](https://docs.medusajs.com/resources/references/medusa-config#storefrontUrl/index.html.md) configuration in `medusa-config.ts`.
+For example, consider the following UI route created at `src/admin/routes/custom/page.tsx`:
-***
+```tsx title="src/admin/routes/custom/page.tsx" highlights={loaderHighlights}
+import { Container, Heading } from "@medusajs/ui"
+import {
+ useLoaderData,
+} from "react-router-dom"
-## Admin Translations
+export async function loader() {
+ // TODO fetch products
-The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions.
+ return {
+ products: [],
+ }
+}
-Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/resources/contribution-guidelines/admin-translations/index.html.md).
+const CustomPage = () => {
+ const { products } = useLoaderData() as Awaited>
+ return (
+
+
+
+ Products count: {products.length}
+
+
+
+ )
+}
-# Environment Variables in Admin Customizations
+export default CustomPage
+```
-In this chapter, you'll learn how to use environment variables in your admin customizations.
+In this example, you first export a `loader` function that can be used to fetch data, such as products. The function returns an object with a `products` property.
-To learn how envirnment variables are generally loaded in Medusa based on your application's environment, check out [this chapter](https://docs.medusajs.com/learn/fundamentals/environment-variables/index.html.md).
+Then, in the `CustomPage` route component, you use the `useLoaderData` hook from React Router to access the data returned by the `loader` function. You can then use the data in your component.
-## How to Set Environment Variables
+### Route Parameters
-The Medusa Admin is built on top of [Vite](https://vite.dev/). To set an environment variable that you want to use in a widget or UI route, prefix the environment variable with `VITE_`.
+You can also access route params in the loader function. For example, consider the following UI route created at `src/admin/routes/custom/[id]/page.tsx`:
-For example:
+```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={loaderParamHighlights}
+import { Container, Heading } from "@medusajs/ui"
+import {
+ useLoaderData,
+ LoaderFunctionArgs,
+} from "react-router-dom"
-```plain
-VITE_MY_API_KEY=sk_123
+export async function loader({ params }: LoaderFunctionArgs) {
+ const { id } = params
+ // TODO fetch product by id
+
+ return {
+ id,
+ }
+}
+
+const CustomPage = () => {
+ const { id } = useLoaderData() as Awaited>
+
+ return (
+
+
+
+ Product ID: {id}
+
+
+
+ )
+}
+
+export default CustomPage
```
+Because the UI route has a route parameter `[id]`, you can access the `id` parameter in the `loader` function. The loader function accepts as a parameter an object of type `LoaderFunctionArgs` from React Router. This object has a `params` property that contains the route parameters.
+
+In the loader, you can fetch data asynchronously using the route parameter and return it. Then, in the route component, you can access the data using the `useLoaderData` hook.
+
+### When to Use Route Loaders
+
+A route loader is executed before the route is loaded. So, it will block navigation until the loader function is resolved.
+
+Only use route loaders when the route component needs data essential before rendering. Otherwise, use the JS SDK with Tanstack (React) Query as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md). This way, you can fetch data asynchronously and update the UI when the data is available. You can also use a loader to prepare some initial data that's used in the route component before the data is retrieved.
+
***
-## How to Use Environment Variables
+## Other React Router Utilities
-To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object.
+### Route Handles
+
+Route handles are available starting from Medusa v2.5.1.
+
+In your UI route or any route file, you can export a `handle` object to define [route handles](https://reactrouter.com/start/framework/route-module#handle). The object is passed to the loader and route contexts.
For example:
-```tsx highlights={[["8"]]}
+```tsx title="src/admin/routes/custom/page.tsx"
+export const handle = {
+ sandbox: true,
+}
+```
+
+### React Router Components and Hooks
+
+Refer to [react-router-dom’s documentation](https://reactrouter.com/en/6.29.0) for components and hooks that you can use in your admin customizations.
+
+
+# Admin Widgets
+
+In this chapter, you’ll learn more about widgets and how to use them.
+
+## What is an Admin Widget?
+
+The Medusa Admin dashboard's pages are customizable to insert widgets of custom content in pre-defined injection zones. You create these widgets as React components that allow admin users to perform custom actions.
+
+For example, you can add a widget on the product details page that allow admin users to sync products to a third-party service.
+
+***
+
+## How to Create a Widget?
+
+### Prerequisites
+
+- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
+
+You create a widget in a `.tsx` file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component that renders the custom content. The file must also export the widget’s configurations indicating where to insert the widget.
+
+For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
+
+
+
+```tsx title="src/admin/widgets/product-widget.tsx" highlights={widgetHighlights}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
+// The widget
const ProductWidget = () => {
return (
- API Key: {import.meta.env.VITE_MY_API_KEY}
+ Product Widget
)
}
+// The widget's configurations
export const config = defineWidgetConfig({
zone: "product.details.before",
})
@@ -5276,28 +5216,76 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`.
+You export the `ProductWidget` component, which shows the heading `Product Widget`. In the widget, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it.
-### Type Error on import.meta.env
+To export the widget's configurations, you use `defineWidgetConfig` from the Admin Extension SDK. It accepts as a parameter an object with the `zone` property, whose value is a string or an array of strings, each being the name of the zone to inject the widget into.
-If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content:
+In the example above, the widget is injected at the top of a product’s details.
-```ts title="src/admin/vite-env.d.ts"
-///
+The widget component must be created as an arrow function.
+
+### Test the Widget
+
+To test out the widget, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
```
-This file tells TypeScript to recognize the `import.meta.env` object and enhances the types of your custom environment variables.
+Then, open a product’s details page. You’ll find your custom widget at the top of the page.
***
-## Check Node Environment in Admin Customizations
+## Props Passed in Detail Pages
-To check the current environment, Vite exposes two variables:
+Widgets that are injected into a details page receive a `data` prop, which is the main data of the details page.
-- `import.meta.env.DEV`: Returns `true` if the current environment is development.
-- `import.meta.env.PROD`: Returns `true` if the current environment is production.
+For example, a widget injected into the `product.details.before` zone receives the product's details in the `data` prop:
-Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode).
+```tsx title="src/admin/widgets/product-widget.tsx" highlights={detailHighlights}
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+import {
+ DetailWidgetProps,
+ AdminProduct,
+} from "@medusajs/framework/types"
+
+// The widget
+const ProductWidget = ({
+ data,
+}: DetailWidgetProps) => {
+ return (
+
+
+
+ Product Widget {data.title}
+
+
+
+ )
+}
+
+// The widget's configurations
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+The props type is `DetailWidgetProps`, and it accepts as a type argument the expected type of `data`. For the product details page, it's `AdminProduct`.
+
+***
+
+## Injection Zone
+
+Refer to [this reference](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md) for the full list of injection zones and their props.
+
+***
+
+## Admin Components List
+
+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.
# Admin UI Routes
@@ -5735,103 +5723,105 @@ createProductsWorkflow.hooks.productsCreated(
This updates the products to their original state before adding the brand to their `metadata` property.
-# Admin Widgets
+# Admin Development Tips
-In this chapter, you’ll learn more about widgets and how to use them.
+In this chapter, you'll find some tips for your admin development.
-## What is an Admin Widget?
+## Send Requests to API Routes
-The Medusa Admin dashboard's pages are customizable to insert widgets of custom content in pre-defined injection zones. You create these widgets as React components that allow admin users to perform custom actions.
+To send a request to an API route in the Medusa Application, use Medusa's [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) with [Tanstack Query](https://tanstack.com/query/latest). Both of these tools are installed in your project by default.
-For example, you can add a widget on the product details page that allow admin users to sync products to a third-party service.
+Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
-***
+First, create the file `src/admin/lib/config.ts` to setup the SDK for use in your customizations:
-## How to Create a Widget?
+```ts
+import Medusa from "@medusajs/js-sdk"
-### Prerequisites
+export const sdk = new Medusa({
+ baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
+ debug: import.meta.env.DEV,
+ auth: {
+ type: "session",
+ },
+})
+```
-- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
+Notice that you use `import.meta.env` to access environment variables in your customizations, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md).
-You create a widget in a `.tsx` file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component that renders the custom content. The file must also export the widget’s configurations indicating where to insert the widget.
+Learn more about the JS SDK's configurations [this documentation](https://docs.medusajs.com/resources/js-sdk#js-sdk-configurations/index.html.md).
-For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
+Then, use the configured SDK with the `useQuery` Tanstack Query hook to send `GET` requests, and `useMutation` hook to send `POST` or `DELETE` requests.
-
+For example:
-```tsx title="src/admin/widgets/product-widget.tsx" highlights={widgetHighlights}
+### Query
+
+```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
+import { Button, Container } from "@medusajs/ui"
+import { useQuery } from "@tanstack/react-query"
+import { sdk } from "../lib/config"
+import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types"
-// The widget
const ProductWidget = () => {
+ const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list(),
+ queryKey: ["products"],
+ })
+
return (
-
+ )}
)
}
-// The widget's configurations
export const config = defineWidgetConfig({
- zone: "product.details.before",
+ zone: "product.list.before",
})
export default ProductWidget
```
-You export the `ProductWidget` component, which shows the heading `Product Widget`. In the widget, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it.
-
-To export the widget's configurations, you use `defineWidgetConfig` from the Admin Extension SDK. It accepts as a parameter an object with the `zone` property, whose value is a string or an array of strings, each being the name of the zone to inject the widget into.
-
-In the example above, the widget is injected at the top of a product’s details.
+### Mutation
-The widget component must be created as an arrow function.
-
-### Test the Widget
-
-To test out the widget, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, open a product’s details page. You’ll find your custom widget at the top of the page.
-
-***
-
-## Props Passed in Detail Pages
-
-Widgets that are injected into a details page receive a `data` prop, which is the main data of the details page.
-
-For example, a widget injected into the `product.details.before` zone receives the product's details in the `data` prop:
-
-```tsx title="src/admin/widgets/product-widget.tsx" highlights={detailHighlights}
+```tsx title="src/admin/widgets/product-widget.ts" highlights={mutationHighlights}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-import {
- DetailWidgetProps,
- AdminProduct,
-} from "@medusajs/framework/types"
+import { Button, Container } from "@medusajs/ui"
+import { useMutation } from "@tanstack/react-query"
+import { sdk } from "../lib/config"
+import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types"
-// The widget
const ProductWidget = ({
- data,
-}: DetailWidgetProps) => {
+ data: productData,
+}: DetailWidgetProps) => {
+ const { mutateAsync } = useMutation({
+ mutationFn: (payload: HttpTypes.AdminUpdateProduct) =>
+ sdk.admin.product.update(productData.id, payload),
+ onSuccess: () => alert("updated product"),
+ })
+
+ const handleUpdate = () => {
+ mutateAsync({
+ title: "New Product Title",
+ })
+ }
+
return (
-
-
- Product Widget {data.title}
-
-
+
)
}
-// The widget's configurations
export const config = defineWidgetConfig({
zone: "product.details.before",
})
@@ -5839,207 +5829,136 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-The props type is `DetailWidgetProps`, and it accepts as a type argument the expected type of `data`. For the product details page, it's `AdminProduct`.
-
-***
+You can also send requests to custom routes as explained in the [JS SDK reference](https://docs.medusajs.com/resources/js-sdk/index.html.md).
-## Injection Zone
+### Use Route Loaders for Initial Data
-Refer to [this reference](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md) for the full list of injection zones and their props.
+You may need to retrieve data before your component is rendered, or you may need to pass some initial data to your component to be used while data is being fetched. In those cases, you can use a [route loader](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
***
-## Admin Components List
-
-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.
+## Global Variables in Admin Customizations
+In your admin customizations, you can use the following global variables:
-# Seed Data with Custom CLI Script
+- `__BASE__`: The base path of the Medusa Admin, as set in the [admin.path](https://docs.medusajs.com/resources/references/medusa-config#path/index.html.md) configuration in `medusa-config.ts`.
+- `__BACKEND_URL__`: The URL to the Medusa backend, as set in the [admin.backendUrl](https://docs.medusajs.com/resources/references/medusa-config#backendurl/index.html.md) configuration in `medusa-config.ts`.
+- `__STOREFRONT_URL__`: The URL to the storefront, as set in the [admin.storefrontUrl](https://docs.medusajs.com/resources/references/medusa-config#storefrontUrl/index.html.md) configuration in `medusa-config.ts`.
-In this chapter, you'll learn how to seed data using a custom CLI script.
+***
-## How to Seed Data
+## Admin Translations
-To seed dummy data for development or demo purposes, use a custom CLI script.
+The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions.
-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.
+Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/resources/contribution-guidelines/admin-translations/index.html.md).
-### Example: Seed Dummy Products
-In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
+# Throwing and Handling Errors
-First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
+In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application.
-```bash npm2yarn
-npm install --save-dev @faker-js/faker
-```
+## Throw MedusaError
-Then, create the file `src/scripts/demo-products.ts` with the following content:
+When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework.
-```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"
+The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response.
-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
- )
+For example:
- const defaultSalesChannel = await salesChannelModuleService
- .listSalesChannels({
- name: "Default Sales Channel",
- })
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { MedusaError } from "@medusajs/framework/utils"
- const sizeOptions = ["S", "M", "L", "XL"]
- const colorOptions = ["Black", "White"]
- const currency_code = "eur"
- const productsNum = 50
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ if (!req.query.q) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "The `q` query parameter is required."
+ )
+ }
- // 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,
- },
- ],
- }
-})
+The `MedusaError` class accepts in its constructor two parameters:
-// TODO seed products
-```
+1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
+2. The second is the message to show in the error response.
-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.
+### Error Object in Response
-Then, replace the new `TODO` with the following:
+The error object returned in the response has two properties:
-```ts title="src/scripts/demo-products.ts"
-const { result: products } = await createProductsWorkflow(container).run({
- input: {
- products: productsData,
- },
-})
+- `type`: The error's type.
+- `message`: The error message, if available.
+- `code`: A common snake-case code. Its values can be:
+ - `invalid_request_error` for the `DUPLICATE_ERROR` type.
+ - `api_error`: for the `DB_ERROR` type.
+ - `invalid_state_error` for `CONFLICT` error type.
+ - `unknown_error` for any unidentified error type.
+ - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
-logger.info(`Seeded ${products.length} products.`)
+### MedusaError Types
-// TODO add inventory levels
-```
+|Type|Description|Status Code|
+|---|---|---|---|---|
+|\`DB\_ERROR\`|Indicates a database error.|\`500\`|
+|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`|
+|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`|
+|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`|
+|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`|
+|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`|
+|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`|
+|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`|
+|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`|
+|Other error types|Any other error type results in an |\`500\`|
-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:
+## Override Error Handler
-```ts title="src/scripts/demo-products.ts"
-logger.info("Seeding inventory levels.")
+The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes.
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: ["id"],
-})
+This error handler will also be used for errors thrown in Medusa's API routes and resources.
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: ["id"],
-})
+For example, create `src/api/middlewares.ts` with the following:
-const inventoryLevels = inventoryItems.map((inventoryItem) => ({
- location_id: stockLocations[0].id,
- stocked_quantity: 1000000,
- inventory_item_id: inventoryItem.id,
-}))
+```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports"
+import {
+ defineMiddlewares,
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { MedusaError } from "@medusajs/framework/utils"
-await createInventoryLevelsWorkflow(container).run({
- input: {
- inventory_levels: inventoryLevels,
+export default defineMiddlewares({
+ errorHandler: (
+ error: MedusaError | any,
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ res.status(400).json({
+ error: "Something happened.",
+ })
},
})
-
-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:
+The `errorHandler` property's value is a function that accepts four parameters:
-```bash
-npx medusa exec ./src/scripts/demo-products.ts
-```
+1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
+2. A request object of type `MedusaRequest`.
+3. A response object of type `MedusaResponse`.
+4. A function of type MedusaNextFunction that executes the next middleware in the stack.
-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.
+This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
# Handling CORS in API Routes
@@ -6197,502 +6116,188 @@ This adds two API Routes:
- A `POST` route at `http://localhost:9000/hello-world`.
-# Middlewares
+# API Route Parameters
-In this chapter, you’ll learn about middlewares and how to create them.
+In this chapter, you’ll learn about path, query, and request body parameters.
-## What is a Middleware?
+## Path Parameters
-A middleware is a function executed when a request is sent to an API Route. It's executed before the route handler function.
+To create an API route that accepts a path parameter, create a directory within the route file's path whose name is of the format `[param]`.
-Middlwares are used to guard API routes, parse request content types other than `application/json`, manipulate request data, and more.
+For example, to create an API Route at the path `/hello-world/:id`, where `:id` is a path parameter, create the file `src/api/hello-world/[id]/route.ts` with the following content:
-As Medusa's server is based on Express, you can use any [Express middleware](https://expressjs.com/en/resources/middleware.html).
+```ts title="src/api/hello-world/[id]/route.ts" highlights={singlePathHighlights}
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: `[GET] Hello ${req.params.id}!`,
+ })
+}
+```
+
+The `MedusaRequest` object has a `params` property. `params` holds the path parameters in key-value pairs.
+
+### Multiple Path Parameters
+
+To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
+
+For example, to create an API route at `/hello-world/:id/name/:name`, create the file `src/api/hello-world/[id]/name/[name]/route.ts` with the following content:
+
+```ts title="src/api/hello-world/[id]/name/[name]/route.ts" highlights={multiplePathHighlights}
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: `[GET] Hello ${
+ req.params.id
+ } - ${req.params.name}!`,
+ })
+}
+```
+
+You access the `id` and `name` path parameters using the `req.params` property.
***
-## How to Create a Middleware?
+## Query Parameters
-Middlewares are defined in the special file `src/api/middlewares.ts`. Use the `defineMiddlewares` function from the Medusa Framework to define the middlewares, and export its value.
+You can access all query parameters in the `query` property of the `MedusaRequest` object. `query` is an object of key-value pairs, where the key is a query parameter's name, and the value is its value.
For example:
-```ts title="src/api/middlewares.ts"
-import {
- defineMiddlewares,
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
+```ts title="src/api/hello-world/route.ts" highlights={queryHighlights}
+import type {
+ MedusaRequest,
+ MedusaResponse,
} from "@medusajs/framework/http"
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/custom*",
- middlewares: [
- (
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- console.log("Received a request!")
-
- next()
- },
- ],
- },
- ],
-})
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: `Hello ${req.query.name}`,
+ })
+}
```
-The `defineMiddlewares` function accepts a middleware configurations object that has the property `routes`. `routes`'s value is an array of middleware route objects, each having the following properties:
+The value of `req.query.name` is the value passed in `?name=John`, for example.
-- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. The regular expression must be compatible with [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
-- `middlewares`: An array of middleware functions.
+### Validate Query Parameters
+
+You can apply validation rules on received query parameters to ensure they match specified rules and types.
-In the example above, you define a middleware that logs the message `Received a request!` whenever a request is sent to an API route path starting with `/custom`.
+Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation#how-to-validate-request-query-paramters/index.html.md).
***
-## Test the Middleware
+## Request Body Parameters
-To test the middleware:
+The Medusa application parses the body of any request having a JSON, URL-encoded, or text request content types. The request body parameters are set in the `MedusaRequest`'s `body` property.
-1. Start the application:
+Learn more about configuring body parsing in [this guide](https://docs.medusajs.com/learn/fundamentals/api-routes/parse-body/index.html.md).
-```bash npm2yarn
-npm run dev
-```
+For example:
-2. Send a request to any API route starting with `/custom`.
-3. See the following message in the terminal:
+```ts title="src/api/hello-world/route.ts" highlights={bodyHighlights}
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
-```bash
-Received a request!
+type HelloWorldReq = {
+ name: string
+}
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: `[POST] Hello ${req.body.name}!`,
+ })
+}
```
-***
+In this example, you use the `name` request body parameter to create the message in the returned response.
-## When to Use Middlewares
+The `MedusaRequest` type accepts a type argument that indicates the type of the request body. This is useful for auto-completion and to avoid typing errors.
-- You want to protect API routes by a custom condition.
-- You're modifying the request body.
+To test it out, send the following request to your Medusa application:
-***
+```bash
+curl -X POST 'http://localhost:9000/hello-world' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "name": "John"
+}'
+```
-## Middleware Function Parameters
+This returns the following JSON object:
-The middleware function accepts three parameters:
+```json
+{
+ "message": "[POST] Hello John!"
+}
+```
-1. A request object of type `MedusaRequest`.
-2. A response object of type `MedusaResponse`.
-3. A function of type `MedusaNextFunction` that executes the next middleware in the stack.
+### Validate Body Parameters
-You must call the `next` function in the middleware. Otherwise, other middlewares and the API route handler won’t execute.
+You can apply validation rules on received body parameters to ensure they match specified rules and types.
-***
+Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation#how-to-validate-request-body/index.html.md).
-## Middleware for Routes with Path Parameters
-To indicate a path parameter in a middleware's `matcher` pattern, use the format `:{param-name}`.
+# Configure Request Body Parser
-For example:
+In this chapter, you'll learn how to configure the request body parser for your API routes.
-```ts title="src/api/middlewares.ts" collapsibleLines="1-7" expandMoreLabel="Show Imports" highlights={pathParamHighlights}
-import {
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
- defineMiddlewares,
-} from "@medusajs/framework/http"
+## Default Body Parser Configuration
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/custom/:id",
- middlewares: [
- // ...
- ],
- },
- ],
-})
-```
+The Medusa application configures the body parser by default to parse JSON, URL-encoded, and text request content types. You can parse other data types by adding the relevant [Express middleware](https://expressjs.com/en/guide/using-middleware.html) or preserve the raw body data by configuring the body parser, which is useful for webhook requests.
-This applies a middleware to the routes defined in the file `src/api/custom/[id]/route.ts`.
+This chapter shares some examples of configuring the body parser for different data types or use cases.
***
-## Restrict HTTP Methods
+## Preserve Raw Body Data for Webhooks
-Restrict which HTTP methods the middleware is applied to using the `method` property of the middleware route object.
+If your API route receives webhook requests, you might want to preserve the raw body data. To do this, you can configure the body parser to parse the raw body data and store it in the `req.rawBody` property.
-For example:
+To do that, create the file `src/api/middlewares.ts` with the following content:
-```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import {
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
- defineMiddlewares,
-} from "@medusajs/framework/http"
+```ts title="src/api/middlewares.ts" highlights={preserveHighlights}
+import { defineMiddlewares } from "@medusajs/framework/http"
export default defineMiddlewares({
routes: [
{
- matcher: "/custom*",
- method: ["POST", "PUT"],
- middlewares: [
- // ...
- ],
+ method: ["POST"],
+ bodyParser: { preserveRawBody: true },
+ matcher: "/custom",
},
],
})
```
-`method`'s value is one or more HTTP methods to apply the middleware to.
+The middleware route object passed to `routes` accepts a `bodyParser` property whose value is an object of configuration for the default body parser. By enabling the `preserveRawBody` property, the raw body data is preserved and stored in the `req.rawBody` property.
-This example applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/custom`.
+Learn more about [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md).
-***
-
-## Request URLs with Trailing Backslashes
-
-A middleware whose `matcher` pattern doesn't end with a backslash won't be applied for requests to URLs with a trailing backslash.
-
-For example, consider you have the following middleware:
-
-```ts collapsibleLines="1-7" expandMoreLabel="Show Imports"
-import {
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
- defineMiddlewares,
-} from "@medusajs/framework/http"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/custom",
- middlewares: [
- (
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- console.log("Received a request!")
-
- next()
- },
- ],
- },
- ],
-})
-```
-
-If you send a request to `http://localhost:9000/custom`, the middleware will run.
-
-However, if you send a request to `http://localhost:9000/custom/`, the middleware won't run.
-
-In general, avoid adding trailing backslashes when sending requests to API routes.
-
-***
-
-## Middlewares Precedence in Registration
-
-The Medusa application registers your middlewares first, then registers middlewares defined in Medusa's core.
-
-So, if you add a middleware for a route defined in the core, it might get overridden by the core middleware. For example, if you add a middleware to change authentication of admin routes, the authentication middleware defined in the core will still run, leading to your middleware not being effective.
-
-
-# Throwing and Handling Errors
-
-In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application.
-
-## Throw MedusaError
-
-When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework.
-
-The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response.
-
-For example:
-
-```ts
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-import { MedusaError } from "@medusajs/framework/utils"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- if (!req.query.q) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "The `q` query parameter is required."
- )
- }
-
- // ...
-}
-```
-
-The `MedusaError` class accepts in its constructor two parameters:
-
-1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
-2. The second is the message to show in the error response.
-
-### Error Object in Response
-
-The error object returned in the response has two properties:
-
-- `type`: The error's type.
-- `message`: The error message, if available.
-- `code`: A common snake-case code. Its values can be:
- - `invalid_request_error` for the `DUPLICATE_ERROR` type.
- - `api_error`: for the `DB_ERROR` type.
- - `invalid_state_error` for `CONFLICT` error type.
- - `unknown_error` for any unidentified error type.
- - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
-
-### MedusaError Types
-
-|Type|Description|Status Code|
-|---|---|---|---|---|
-|\`DB\_ERROR\`|Indicates a database error.|\`500\`|
-|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`|
-|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`|
-|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`|
-|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`|
-|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`|
-|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`|
-|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`|
-|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`|
-|Other error types|Any other error type results in an |\`500\`|
-
-***
-
-## Override Error Handler
-
-The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes.
-
-This error handler will also be used for errors thrown in Medusa's API routes and resources.
-
-For example, create `src/api/middlewares.ts` with the following:
-
-```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports"
-import {
- defineMiddlewares,
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { MedusaError } from "@medusajs/framework/utils"
-
-export default defineMiddlewares({
- errorHandler: (
- error: MedusaError | any,
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- res.status(400).json({
- error: "Something happened.",
- })
- },
-})
-```
-
-The `errorHandler` property's value is a function that accepts four parameters:
-
-1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
-2. A request object of type `MedusaRequest`.
-3. A response object of type `MedusaResponse`.
-4. A function of type MedusaNextFunction that executes the next middleware in the stack.
-
-This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
-
-
-# API Route Parameters
-
-In this chapter, you’ll learn about path, query, and request body parameters.
-
-## Path Parameters
-
-To create an API route that accepts a path parameter, create a directory within the route file's path whose name is of the format `[param]`.
-
-For example, to create an API Route at the path `/hello-world/:id`, where `:id` is a path parameter, create the file `src/api/hello-world/[id]/route.ts` with the following content:
-
-```ts title="src/api/hello-world/[id]/route.ts" highlights={singlePathHighlights}
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: `[GET] Hello ${req.params.id}!`,
- })
-}
-```
-
-The `MedusaRequest` object has a `params` property. `params` holds the path parameters in key-value pairs.
-
-### Multiple Path Parameters
-
-To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
-
-For example, to create an API route at `/hello-world/:id/name/:name`, create the file `src/api/hello-world/[id]/name/[name]/route.ts` with the following content:
-
-```ts title="src/api/hello-world/[id]/name/[name]/route.ts" highlights={multiplePathHighlights}
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: `[GET] Hello ${
- req.params.id
- } - ${req.params.name}!`,
- })
-}
-```
-
-You access the `id` and `name` path parameters using the `req.params` property.
-
-***
-
-## Query Parameters
-
-You can access all query parameters in the `query` property of the `MedusaRequest` object. `query` is an object of key-value pairs, where the key is a query parameter's name, and the value is its value.
-
-For example:
-
-```ts title="src/api/hello-world/route.ts" highlights={queryHighlights}
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: `Hello ${req.query.name}`,
- })
-}
-```
-
-The value of `req.query.name` is the value passed in `?name=John`, for example.
-
-### Validate Query Parameters
-
-You can apply validation rules on received query parameters to ensure they match specified rules and types.
-
-Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation#how-to-validate-request-query-paramters/index.html.md).
-
-***
-
-## Request Body Parameters
-
-The Medusa application parses the body of any request having a JSON, URL-encoded, or text request content types. The request body parameters are set in the `MedusaRequest`'s `body` property.
-
-Learn more about configuring body parsing in [this guide](https://docs.medusajs.com/learn/fundamentals/api-routes/parse-body/index.html.md).
-
-For example:
-
-```ts title="src/api/hello-world/route.ts" highlights={bodyHighlights}
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-type HelloWorldReq = {
- name: string
-}
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: `[POST] Hello ${req.body.name}!`,
- })
-}
-```
-
-In this example, you use the `name` request body parameter to create the message in the returned response.
-
-The `MedusaRequest` type accepts a type argument that indicates the type of the request body. This is useful for auto-completion and to avoid typing errors.
-
-To test it out, send the following request to your Medusa application:
-
-```bash
-curl -X POST 'http://localhost:9000/hello-world' \
--H 'Content-Type: application/json' \
---data-raw '{
- "name": "John"
-}'
-```
-
-This returns the following JSON object:
-
-```json
-{
- "message": "[POST] Hello John!"
-}
-```
-
-### Validate Body Parameters
-
-You can apply validation rules on received body parameters to ensure they match specified rules and types.
-
-Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation#how-to-validate-request-body/index.html.md).
-
-
-# Configure Request Body Parser
-
-In this chapter, you'll learn how to configure the request body parser for your API routes.
-
-## Default Body Parser Configuration
-
-The Medusa application configures the body parser by default to parse JSON, URL-encoded, and text request content types. You can parse other data types by adding the relevant [Express middleware](https://expressjs.com/en/guide/using-middleware.html) or preserve the raw body data by configuring the body parser, which is useful for webhook requests.
-
-This chapter shares some examples of configuring the body parser for different data types or use cases.
-
-***
-
-## Preserve Raw Body Data for Webhooks
-
-If your API route receives webhook requests, you might want to preserve the raw body data. To do this, you can configure the body parser to parse the raw body data and store it in the `req.rawBody` property.
-
-To do that, create the file `src/api/middlewares.ts` with the following content:
-
-```ts title="src/api/middlewares.ts" highlights={preserveHighlights}
-import { defineMiddlewares } from "@medusajs/framework/http"
-
-export default defineMiddlewares({
- routes: [
- {
- method: ["POST"],
- bodyParser: { preserveRawBody: true },
- matcher: "/custom",
- },
- ],
-})
-```
-
-The middleware route object passed to `routes` accepts a `bodyParser` property whose value is an object of configuration for the default body parser. By enabling the `preserveRawBody` property, the raw body data is preserved and stored in the `req.rawBody` property.
-
-Learn more about [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md).
-
-You can then access the raw body data in your API route handler:
+You can then access the raw body data in your API route handler:
```ts title="src/api/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
@@ -6827,266 +6432,512 @@ 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.
-# Protected Routes
+# Middlewares
-In this chapter, you’ll learn how to create protected routes.
+In this chapter, you’ll learn about middlewares and how to create them.
-## What is a Protected Route?
+## What is a Middleware?
-A protected route is a route that requires requests to be user-authenticated before performing the route's functionality. Otherwise, the request fails, and the user is prevented access.
+A middleware is a function executed when a request is sent to an API Route. It's executed before the route handler function.
-***
+Middlwares are used to guard API routes, parse request content types other than `application/json`, manipulate request data, and more.
-## Default Protected Routes
+As Medusa's server is based on Express, you can use any [Express middleware](https://expressjs.com/en/resources/middleware.html).
-Medusa applies an authentication guard on routes starting with `/admin`, including custom API routes.
+### Middleware Types
-Requests to `/admin` must be user-authenticated to access the route.
+There are two types of middlewares:
-Refer to the API Reference for [Admin](https://docs.medusajs.com/api/admin#authentication) and [Store](https://docs.medusajs.com/api/store#authentication) authentication methods.
+1. Global Middleware: A middleware that applies to all routes matching a specified pattern.
+2. Route Middleware: A middleware that applies to routes matching a specified pattern and HTTP method(s).
+
+These middlewares generally have the same definition and usage, but they differ in the routes they apply to. You'll learn how to create both types in the following sections.
***
-## Protect Custom API Routes
+## How to Create a Global Middleware?
-To protect custom API Routes to only allow authenticated customer or admin users, use the `authenticate` middleware from the Medusa Framework.
+Middlewares of all types are defined in the special file `src/api/middlewares.ts`. Use the `defineMiddlewares` function from the Medusa Framework to define the middlewares, and export its value.
For example:
-```ts title="src/api/middlewares.ts" highlights={highlights}
+```ts title="src/api/middlewares.ts"
import {
defineMiddlewares,
- authenticate,
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
} from "@medusajs/framework/http"
export default defineMiddlewares({
routes: [
{
- matcher: "/custom/admin*",
- middlewares: [authenticate("user", ["session", "bearer", "api-key"])],
- },
- {
- matcher: "/custom/customer*",
- middlewares: [authenticate("customer", ["session", "bearer"])],
+ matcher: "/custom*",
+ middlewares: [
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ console.log("Received a request!")
+
+ next()
+ },
+ ],
},
],
})
```
-The `authenticate` middleware function accepts three parameters:
+The `defineMiddlewares` function accepts a middleware configurations object that has the property `routes`. `routes`'s value is an array of middleware route objects, each having the following properties:
-1. The type of user authenticating. Use `user` for authenticating admin users, and `customer` for authenticating customers. You can also pass `*` to allow all types of users.
-2. An array of types of authentication methods allowed. Both `user` and `customer` scopes support `session` and `bearer`. The `admin` scope also supports the `api-key` authentication method.
-3. An optional object of configurations accepting the following properties:
- - `allowUnauthenticated`: (default: `false`) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
- - `allowUnregistered` (default: `false`): A boolean indicating if unregistered users should be allowed access. This is useful when you want to allow users who aren’t registered to access certain routes.
+- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. The regular expression must be compatible with [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
+- `middlewares`: An array of global and route middleware functions.
-***
+In the example above, you define a global middleware that logs the message `Received a request!` whenever a request is sent to an API route path starting with `/custom`.
-## Authentication Opt-Out
+### Test the Global Middleware
-To disable the authentication guard on custom routes under the `/admin` path prefix, export an `AUTHENTICATE` variable in the route file with its value set to `false`.
+To test the middleware:
-For example:
+1. Start the application:
-```ts title="src/api/admin/custom/route.ts" highlights={[["15"]]}
-import type {
- AuthenticatedMedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
+```bash npm2yarn
+npm run dev
+```
-export const GET = async (
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "Hello",
- })
-}
+2. Send a request to any API route starting with `/custom`.
+3. See the following message in the terminal:
-export const AUTHENTICATE = false
+```bash
+Received a request!
```
-Now, any request sent to the `/admin/custom` API route is allowed, regardless if the admin user is authenticated.
-
***
-## Authenticated Request Type
-
-To access the authentication details in an API route, such as the logged-in user's ID, set the type of the first request parameter to `AuthenticatedMedusaRequest`. It extends `MedusaRequest`.
-
-The `auth_context.actor_id` property of `AuthenticatedMedusaRequest` holds the ID of the authenticated user or customer. If there isn't any authenticated user or customer, `auth_context` is `undefined`.
-
-If you opt-out of authentication in a route as mentioned in the [previous section](#authentication-opt-out), you can't access the authenticated user or customer anymore. Use the [authenticate middleware](#protect-custom-api-routes) instead.
-
-### Retrieve Logged-In Customer's Details
+## How to Create a Route Middleware?
-You can access the logged-in customer’s ID in all API routes starting with `/store` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
+In the previous section, you learned how to create a global middleware. You define the route middleware in the same way in `src/api/middlewares.ts`, but you specify an additional property `method` in the middleware route object. Its value is one or more HTTP methods to apply the middleware to.
For example:
-```ts title="src/api/store/custom/route.ts" highlights={[["19", "req.auth_context.actor_id", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import type {
- AuthenticatedMedusaRequest,
- MedusaResponse,
+```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import {
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+ defineMiddlewares,
} from "@medusajs/framework/http"
-import { Modules } from "@medusajs/framework/utils"
-import { ICustomerModuleService } from "@medusajs/framework/types"
-
-export const GET = async (
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-) => {
- if (req.auth_context?.actor_id) {
- // retrieve customer
- const customerModuleService: ICustomerModuleService = req.scope.resolve(
- Modules.CUSTOMER
- )
- const customer = await customerModuleService.retrieveCustomer(
- req.auth_context.actor_id
- )
- }
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom*",
+ method: ["POST", "PUT"],
+ middlewares: [
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ console.log("Received a request!")
- // ...
-}
+ next()
+ },
+ ],
+ },
+ ],
+})
```
-In this example, you resolve the Customer Module's main service, then use it to retrieve the logged-in customer, if available.
-
-### Retrieve Logged-In Admin User's Details
+This example applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/custom`, changing the middleware from a global middleware to a route middleware.
-You can access the logged-in admin user’s ID in all API Routes starting with `/admin` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
+### Test the Route Middleware
-For example:
+To test the middleware:
-```ts title="src/api/admin/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import type {
- AuthenticatedMedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { Modules } from "@medusajs/framework/utils"
-import { IUserModuleService } from "@medusajs/framework/types"
+1. Start the application:
-export const GET = async (
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-) => {
- const userModuleService: IUserModuleService = req.scope.resolve(
- Modules.USER
- )
+```bash npm2yarn
+npm run dev
+```
- const user = await userModuleService.retrieveUser(
- req.auth_context.actor_id
- )
+2. Send a `POST` request to any API route starting with `/custom`.
+3. See the following message in the terminal:
- // ...
-}
+```bash
+Received a request!
```
-In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
+***
+## When to Use Middlewares
-# API Route Response
+- You want to protect API routes by a custom condition.
+- You're modifying the request body.
-In this chapter, you'll learn how to send a response in your API route.
+***
-## Send a JSON Response
+## Middleware Function Parameters
-To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
+The middleware function accepts three parameters:
+
+1. A request object of type `MedusaRequest`.
+2. A response object of type `MedusaResponse`.
+3. A function of type `MedusaNextFunction` that executes the next middleware in the stack.
+
+You must call the `next` function in the middleware. Otherwise, other middlewares and the API route handler won’t execute.
+
+***
+
+## Middleware for Routes with Path Parameters
+
+To indicate a path parameter in a middleware's `matcher` pattern, use the format `:{param-name}`.
For example:
-```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+```ts title="src/api/middlewares.ts" collapsibleLines="1-7" expandMoreLabel="Show Imports" highlights={pathParamHighlights}
+import {
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "Hello, World!",
- })
-}
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom/:id",
+ middlewares: [
+ // ...
+ ],
+ },
+ ],
+})
```
-This API route returns the following JSON object:
+This applies a middleware to the routes defined in the file `src/api/custom/[id]/route.ts`.
-```json
-{
- "message": "Hello, World!"
-}
+***
+
+## Request URLs with Trailing Backslashes
+
+A middleware whose `matcher` pattern doesn't end with a backslash won't be applied for requests to URLs with a trailing backslash.
+
+For example, consider you have the following middleware:
+
+```ts title="src/api/middlewares.ts" collapsibleLines="1-7" expandMoreLabel="Show Imports"
+import {
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom",
+ middlewares: [
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ console.log("Received a request!")
+
+ next()
+ },
+ ],
+ },
+ ],
+})
```
+If you send a request to `http://localhost:9000/custom`, the middleware will run.
+
+However, if you send a request to `http://localhost:9000/custom/`, the middleware won't run.
+
+In general, avoid adding trailing backslashes when sending requests to API routes.
+
***
-## Set Response Status Code
+## Middlewares and Route Ordering
-By default, setting the JSON data using the `json` method returns a response with a `200` status code.
+The ordering explained in this section was added in [Medusa v2.6](https://github.com/medusajs/medusa/releases/tag/v2.6)
-To change the status code, use the `status` method of the `MedusaResponse` object.
+The Medusa application registers middlewares and API route handlers in the following order:
+
+1. Global middlewares in the following order:
+ 1. Global middleware defined in the Medusa's core.
+ 2. Global middleware defined in the plugins (in the order the plugins are registered in).
+ 3. Global middleware you define in the application.
+2. Route middlewares in the following order:
+ 1. Route middleware defined in the Medusa's core.
+ 2. Route middleware defined in the plugins (in the order the plugins are registered in).
+ 3. Route middleware you define in the application.
+3. API routes in the following order:
+ 1. API routes defined in the Medusa's core.
+ 2. API routes defined in the plugins (in the order the plugins are registered in).
+ 3. API routes you define in the application.
+
+### Middlewares Sorting
+
+On top of the previous ordering, Medusa sorts global and route middlewares based on their matcher pattern in the following order:
+
+1. Wildcard matchers. For example, `/custom*`.
+2. Regex matchers. For example, `/custom/(products|collections)`.
+3. Static matchers without parameters. For example, `/custom`.
+4. Static matchers with parameters. For example, `/custom/:id`.
+
+For example, if you have the following middlewares:
+
+```ts title="src/api/middlewares.ts"
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom/:id",
+ middlewares: [/* ... */],
+ },
+ {
+ matcher: "/custom",
+ middlewares: [/* ... */],
+ },
+ {
+ matcher: "/custom*",
+ method: ["GET"],
+ middlewares: [/* ... */],
+ },
+ {
+ matcher: "/custom/:id",
+ method: ["GET"],
+ middlewares: [/* ... */],
+ },
+ ],
+})
+```
+
+The global middlewares are sorted into the following order before they're registered:
+
+1. Global middleware `/custom`.
+2. Global middleware `/custom/:id`.
+
+And the route middlewares are sorted into the following order before they're registered:
+
+1. Route middleware `/custom*`.
+2. Route middleware `/custom/:id`.
+
+Then, the middlwares are registered in the order mentioned earlier, with global middlewares first, then the route middlewares.
+
+### Middlewares and Route Execution Order
+
+When a request is sent to an API route, the global middlewares are executed first, then the route middlewares, and finally the route handler.
+
+For example, consider you have the following middlewares:
+
+```ts title="src/api/middlewares.ts"
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom",
+ middlewares: [
+ (req, res, next) => {
+ console.log("Global middleware")
+ next()
+ },
+ ],
+ },
+ {
+ matcher: "/custom",
+ method: ["GET"],
+ middlewares: [
+ (req, res, next) => {
+ console.log("Route middleware")
+ next()
+ },
+ ],
+ },
+ ],
+})
+```
+
+When you send a request to `/custom` route, the following messages are logged in the terminal:
+
+```bash
+Global middleware
+Route middleware
+Hello from custom! # message logged from API route handler
+```
+
+The global middleware runs first, then the route middleware, and finally the route handler, assuming that it logs the message `Hello from custom!`.
+
+***
+
+## Overriding Middlewares
+
+A middleware can not override an existing middleware. Instead, middlewares are added to the end of the middleware stack.
+
+For example, if you define a custom validation middleware, such as `validateAndTransformBody`, on an existing route, then both the original and the custom validation middleware will run.
+
+
+# Protected Routes
+
+In this chapter, you’ll learn how to create protected routes.
+
+## What is a Protected Route?
+
+A protected route is a route that requires requests to be user-authenticated before performing the route's functionality. Otherwise, the request fails, and the user is prevented access.
+
+***
+
+## Default Protected Routes
+
+Medusa applies an authentication guard on routes starting with `/admin`, including custom API routes.
+
+Requests to `/admin` must be user-authenticated to access the route.
+
+Refer to the API Reference for [Admin](https://docs.medusajs.com/api/admin#authentication) and [Store](https://docs.medusajs.com/api/store#authentication) authentication methods.
+
+***
+
+## Protect Custom API Routes
+
+To protect custom API Routes to only allow authenticated customer or admin users, use the `authenticate` middleware from the Medusa Framework.
For example:
-```ts title="src/api/custom/route.ts" highlights={statusHighlight}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+```ts title="src/api/middlewares.ts" highlights={highlights}
+import {
+ defineMiddlewares,
+ authenticate,
+} from "@medusajs/framework/http"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom/admin*",
+ middlewares: [authenticate("user", ["session", "bearer", "api-key"])],
+ },
+ {
+ matcher: "/custom/customer*",
+ middlewares: [authenticate("customer", ["session", "bearer"])],
+ },
+ ],
+})
+```
+
+The `authenticate` middleware function accepts three parameters:
+
+1. The type of user authenticating. Use `user` for authenticating admin users, and `customer` for authenticating customers. You can also pass `*` to allow all types of users.
+2. An array of types of authentication methods allowed. Both `user` and `customer` scopes support `session` and `bearer`. The `admin` scope also supports the `api-key` authentication method.
+3. An optional object of configurations accepting the following properties:
+ - `allowUnauthenticated`: (default: `false`) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
+ - `allowUnregistered` (default: `false`): A boolean indicating if unregistered users should be allowed access. This is useful when you want to allow users who aren’t registered to access certain routes.
+
+***
+
+## Authentication Opt-Out
+
+To disable the authentication guard on custom routes under the `/admin` path prefix, export an `AUTHENTICATE` variable in the route file with its value set to `false`.
+
+For example:
+
+```ts title="src/api/admin/custom/route.ts" highlights={[["15"]]}
+import type {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
export const GET = async (
- req: MedusaRequest,
+ req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
- res.status(201).json({
- message: "Hello, World!",
+ res.json({
+ message: "Hello",
})
}
+
+export const AUTHENTICATE = false
```
-The response of this API route has the status code `201`.
+Now, any request sent to the `/admin/custom` API route is allowed, regardless if the admin user is authenticated.
***
-## Change Response Content Type
+## Authenticated Request 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.
+To access the authentication details in an API route, such as the logged-in user's ID, set the type of the first request parameter to `AuthenticatedMedusaRequest`. It extends `MedusaRequest`.
-For example, to create an API route that returns an event stream:
+The `auth_context.actor_id` property of `AuthenticatedMedusaRequest` holds the ID of the authenticated user or customer. If there isn't any authenticated user or customer, `auth_context` is `undefined`.
-```ts highlights={streamHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+If you opt-out of authentication in a route as mentioned in the [previous section](#authentication-opt-out), you can't access the authenticated user or customer anymore. Use the [authenticate middleware](#protect-custom-api-routes) instead.
+
+### Retrieve Logged-In Customer's Details
+
+You can access the logged-in customer’s ID in all API routes starting with `/store` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
+
+For example:
+
+```ts title="src/api/store/custom/route.ts" highlights={[["19", "req.auth_context.actor_id", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import type {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
+import { ICustomerModuleService } from "@medusajs/framework/types"
export const GET = async (
- req: MedusaRequest,
+ req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
- res.writeHead(200, {
- "Content-Type": "text/event-stream",
- "Cache-Control": "no-cache",
- Connection: "keep-alive",
- })
+ if (req.auth_context?.actor_id) {
+ // retrieve customer
+ const customerModuleService: ICustomerModuleService = req.scope.resolve(
+ Modules.CUSTOMER
+ )
- const interval = setInterval(() => {
- res.write("Streaming data...\n")
- }, 3000)
+ const customer = await customerModuleService.retrieveCustomer(
+ req.auth_context.actor_id
+ )
+ }
- req.on("end", () => {
- clearInterval(interval)
- res.end()
- })
+ // ...
}
```
-The `writeHead` method accepts two parameters:
+In this example, you resolve the Customer Module's main service, then use it to retrieve the logged-in customer, if available.
-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.
+### Retrieve Logged-In Admin User's Details
-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.
+You can access the logged-in admin user’s ID in all API Routes starting with `/admin` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
-***
+For example:
-## Do More with Responses
+```ts title="src/api/admin/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import type {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
+import { IUserModuleService } from "@medusajs/framework/types"
-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.
+export const GET = async (
+ req: AuthenticatedMedusaRequest,
+ res: MedusaResponse
+) => {
+ const userModuleService: IUserModuleService = req.scope.resolve(
+ Modules.USER
+ )
+
+ const user = await userModuleService.retrieveUser(
+ req.auth_context.actor_id
+ )
+
+ // ...
+}
+```
+
+In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
# Request Body and Query Parameter Validation
@@ -7383,165 +7234,343 @@ 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. */}
-# 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.
+# API Route Response
-***
+In this chapter, you'll learn how to send a response in your API route.
-## How to Set a Check Constraint?
+## Send a JSON Response
-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.
+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, to set a check constraint on a `price` property that ensures its value can only be a positive number:
+For example:
-```ts highlights={checks1Highlights}
-import { model } from "@medusajs/framework/utils"
+```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- (columns) => `${columns.price} >= 0`,
-])
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "Hello, World!",
+ })
+}
```
-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"
+This API route returns the following JSON object:
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- {
- name: "custom_product_price_check",
- expression: (columns) => `${columns.price} >= 0`,
- },
-])
+```json
+{
+ "message": "Hello, World!"
+}
```
-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).
+## Set Response Status Code
-***
+By default, setting the JSON data using the `json` method returns a response with a `200` status code.
-## Apply in Migrations
+To change the status code, use the `status` method of the `MedusaResponse` object.
-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.
+For example:
-To generate a migration for the data model's module then reflect it on the database, run the following command:
+```ts title="src/api/custom/route.ts" highlights={statusHighlight}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-```bash
-npx medusa db:generate custom_module
-npx medusa db:migrate
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.status(201).json({
+ message: "Hello, World!",
+ })
+}
```
-The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
+The response of this API route has the status code `201`.
+***
-# Emit Workflow and Service Events
+## Change Response Content Type
-In this chapter, you'll learn about event types and how to emit an event in a service or workflow.
+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.
-## Event Types
+For example, to create an API route that returns an event stream:
-In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronus action, such as send a notification or data to a third-party system.
+```ts highlights={streamHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-There are two types of events in Medusa:
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.writeHead(200, {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ })
-1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, Medusa emits the `order.placed` event after a cart is completed.
-2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail.
+ const interval = setInterval(() => {
+ res.write("Streaming data...\n")
+ }, 3000)
-### Which Event Type to Use?
+ req.on("end", () => {
+ clearInterval(interval)
+ res.end()
+ })
+}
+```
-**Workflow events** are the most common event type in development, as most custom features and customizations are built around workflows.
+The `writeHead` method accepts two parameters:
-Some examples of workflow events:
+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.
-1. When a user creates a blog post and you're emitting an event to send a newsletter email.
-2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added.
-3. When a customer purchases a digital product and you want to generate and send it to them.
+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.
-You should only go for a **service event** if you're emitting an event for processes under the hood that don't directly affect front-facing features.
+***
-Some examples of service events:
+## Do More with Responses
-1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed.
-2. When you're syncing data with a search engine.
+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.
-***
-## Emit Event in a Workflow
+# Add Columns to a Link
-To emit a workflow event, use the `emitEventStep` helper step provided in the `@medusajs/medusa/core-flows` package.
+In this chapter, you'll learn how to add custom columns to a link definition and manage them.
-For example:
+## How to Add Custom Columns to a Link's Table?
-```ts highlights={highlights}
-import {
- createWorkflow,
-} from "@medusajs/framework/workflows-sdk"
-import {
- emitEventStep,
-} from "@medusajs/medusa/core-flows"
+The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
-const helloWorldWorkflow = createWorkflow(
- "hello-world",
- () => {
- // ...
+To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
- emitEventStep({
- eventName: "custom.created",
- data: {
- id: "123",
- // other data payload
+```ts highlights={linkHighlights}
+import HelloModule from "../modules/hello"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.myCustom,
+ {
+ database: {
+ extraColumns: {
+ metadata: {
+ type: "json",
+ },
},
- })
+ },
}
)
```
-The `emitEventStep` accepts an object having the following properties:
-
-- `eventName`: The event's name.
-- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload.
-
-In this example, you emit the event `custom.created` and pass in the data payload an ID property.
-
-### Test it Out
-
-If you execute the workflow, the event is emitted and you can see it in your application's logs.
+This adds to the table created for the link between `product` and `myCustom` a `metadata` column of type `json`.
-Any subscribers listening to the event are executed.
+### Database Options
-***
+The `database` property defines configuration for the table created in the database.
-## Emit Event in a Service
+Its `extraColumns` property defines custom columns to create in the link's table.
-To emit a service event:
+`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
-1. Resolve `event_bus` from the module's container in your service's constructor:
+### Column Configurations
-### Extending Service Factory
+The column's configurations object accepts the following properties:
-```ts title="src/modules/hello/service.ts" highlights={["9"]}
-import { IEventBusService } from "@medusajs/framework/types"
+- `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",
+ },
+ HELLO_MODULE: {
+ my_custom_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 productHelloLink from "../links/product-hello"
+
+// ...
+
+const { data } = await query.graph({
+ entity: productHelloLink.entryPoint,
+ fields: ["metadata", "product.*", "my_custom.*"],
+ filters: {
+ product_id: "prod_123",
+ },
+})
+```
+
+This retrieves the product of id `prod_123` and its linked `my_custom` 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",
+ },
+ HELLO_MODULE: {
+ my_custom_id: "321",
+ },
+ data: {
+ metadata: {
+ test: false,
+ },
+ },
+})
+```
+
+
+# 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.
+
+## Event Types
+
+In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronus action, such as send a notification or data to a third-party system.
+
+There are two types of events in Medusa:
+
+1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, Medusa emits the `order.placed` event after a cart is completed.
+2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail.
+
+### Which Event Type to Use?
+
+**Workflow events** are the most common event type in development, as most custom features and customizations are built around workflows.
+
+Some examples of workflow events:
+
+1. When a user creates a blog post and you're emitting an event to send a newsletter email.
+2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added.
+3. When a customer purchases a digital product and you want to generate and send it to them.
+
+You should only go for a **service event** if you're emitting an event for processes under the hood that don't directly affect front-facing features.
+
+Some examples of service events:
+
+1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed.
+2. When you're syncing data with a search engine.
+
+***
+
+## Emit Event in a Workflow
+
+To emit a workflow event, use the `emitEventStep` helper step provided in the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts highlights={highlights}
+import {
+ createWorkflow,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ emitEventStep,
+} from "@medusajs/medusa/core-flows"
+
+const helloWorldWorkflow = createWorkflow(
+ "hello-world",
+ () => {
+ // ...
+
+ emitEventStep({
+ eventName: "custom.created",
+ data: {
+ id: "123",
+ // other data payload
+ },
+ })
+ }
+)
+```
+
+The `emitEventStep` accepts an object having the following properties:
+
+- `eventName`: The event's name.
+- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload.
+
+In this example, you emit the event `custom.created` and pass in the data payload an ID property.
+
+### Test it Out
+
+If you execute the workflow, the event is emitted and you can see it in your application's logs.
+
+Any subscribers listening to the event are executed.
+
+***
+
+## Emit Event in a Service
+
+To emit a service event:
+
+1. Resolve `event_bus` from the module's container in your service's constructor:
+
+### Extending Service Factory
+
+```ts title="src/modules/hello/service.ts" highlights={["9"]}
+import { IEventBusService } from "@medusajs/framework/types"
import { MedusaService } from "@medusajs/framework/utils"
class HelloModuleService extends MedusaService({
@@ -7623,2052 +7652,2164 @@ If you execute the `performAction` method of your service, the event is emitted
Any subscribers listening to the event are also executed.
-# Configure Data Model Properties
-
-In this chapter, you’ll learn how to configure data model properties.
+# Seed Data with Custom CLI Script
-## Property’s Default Value
+In this chapter, you'll learn how to seed data using a custom CLI script.
-Use the `default` method on a property's definition to specify the default value of a property.
+## How to Seed Data
-For example:
+To seed dummy data for development or demo purposes, use a custom CLI script.
-```ts highlights={defaultHighlights}
-import { model } from "@medusajs/framework/utils"
+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.
-const MyCustom = model.define("my_custom", {
- color: model
- .enum(["black", "white"])
- .default("black"),
- age: model
- .number()
- .default(0),
- // ...
-})
+### Example: Seed Dummy Products
-export default MyCustom
-```
+In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
+First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-***
+```bash npm2yarn
+npm install --save-dev @faker-js/faker
+```
-## Nullable Property
+Then, create the file `src/scripts/demo-products.ts` with the following content:
-Use the `nullable` method to indicate that a property’s value can be `null`.
+```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"
-For example:
+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
+ )
-```ts highlights={nullableHighlights}
-import { model } from "@medusajs/framework/utils"
+ const defaultSalesChannel = await salesChannelModuleService
+ .listSalesChannels({
+ name: "Default Sales Channel",
+ })
-const MyCustom = model.define("my_custom", {
- price: model.bigNumber().nullable(),
- // ...
-})
+ const sizeOptions = ["S", "M", "L", "XL"]
+ const colorOptions = ["Black", "White"]
+ const currency_code = "eur"
+ const productsNum = 50
-export default MyCustom
+ // TODO seed products
+}
```
-***
-
-## Unique Property
-
-The `unique` method indicates that a property’s value must be unique in the database through a unique index.
+So far, in the script, you:
-For example:
+- 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.
-```ts highlights={uniqueHighlights}
-import { model } from "@medusajs/framework/utils"
+Next, replace the `TODO` with the following:
-const User = model.define("user", {
- email: model.text().unique(),
- // ...
+```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,
+ },
+ ],
+ }
})
-export default User
+// TODO seed products
```
-In this example, multiple users can’t have the same email.
+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:
-# Data Model Database Index
+```ts title="src/scripts/demo-products.ts"
+const { result: products } = await createProductsWorkflow(container).run({
+ input: {
+ products: productsData,
+ },
+})
-In this chapter, you’ll learn how to define a database index on a data model.
+logger.info(`Seeded ${products.length} products.`)
-## Define Database Index on Property
+// TODO add inventory levels
+```
-Use the `index` method on a property's definition to define a database index.
+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.
-For example:
+Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
-```ts highlights={highlights}
-import { model } from "@medusajs/framework/utils"
+```ts title="src/scripts/demo-products.ts"
+logger.info("Seeding inventory levels.")
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text().index(
- "IDX_MY_CUSTOM_NAME"
- ),
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: ["id"],
})
-export default MyCustom
-```
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
+ fields: ["id"],
+})
-The `index` method optionally accepts the name of the index as a parameter.
+const inventoryLevels = inventoryItems.map((inventoryItem) => ({
+ location_id: stockLocations[0].id,
+ stocked_quantity: 1000000,
+ inventory_item_id: inventoryItem.id,
+}))
-In this example, you define an index on the `name` property.
+await createInventoryLevelsWorkflow(container).run({
+ input: {
+ inventory_levels: inventoryLevels,
+ },
+})
-***
+logger.info("Finished seeding inventory levels data.")
+```
-## Define Database Index on Data Model
+You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
-A data model has an `indexes` method that defines database indices on its properties.
+Then, you generate inventory levels for each inventory item, associating it with the first stock location.
-The index can be on multiple columns (composite index). For example:
+Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
-```ts highlights={dataModelIndexHighlights}
-import { model } from "@medusajs/framework/utils"
+### Test Script
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- name: model.text(),
- age: model.number(),
-}).indexes([
- {
- on: ["name", "age"],
- },
-])
+To test out the script, run the following command in your project's directory:
-export default MyCustom
+```bash
+npx medusa exec ./src/scripts/demo-products.ts
```
-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:
+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.
-```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,
- },
- },
-])
+# Link
-export default MyCustom
-```
+In this chapter, you’ll learn what Link is and how to use it to manage links.
-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.
+As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below.
-In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
+## What is Link?
-A property's condition can be a negation. For example:
+Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name.
-```ts highlights={negationHighlights}
-import { model } from "@medusajs/framework/utils"
+For example:
-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,
- },
- },
- },
-])
+```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
-export default MyCustom
+export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+): Promise {
+ const link = req.scope.resolve(
+ ContainerRegistrationKeys.LINK
+ )
+
+ // ...
+}
```
-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.
+You can use its methods to manage links, such as create or delete links.
-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
+## Create Link
-The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
+To create a link between records of two data models, use the `create` method of Link.
For example:
-```ts highlights={uniqueHighlights}
-import { model } from "@medusajs/framework/utils"
+```ts
+import { Modules } 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
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ "helloModuleService": {
+ my_custom_id: "mc_123",
+ },
+})
```
-This creates a unique composite index on the `name` and `age` properties.
+The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
+The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
-# Infer Type of Data Model
+The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record.
-In this chapter, you'll learn how to infer the type of a data model.
+So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module.
-## How to Infer Type of Data Model?
+***
-Consider you have a `MyCustom` 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.
+## Dismiss Link
-Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
+To remove a link between records of two data models, use the `dismiss` method of Link.
For example:
```ts
-import { InferTypeOf } from "@medusajs/framework/types"
-import { MyCustom } from "../models/my-custom" // relative path to the model
-
-export type MyCustom = InferTypeOf
-```
-
-`InferTypeOf` accepts as a type argument the type of the data model.
-
-Since the `MyCustom` 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 `MyCustom` 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 { MyCustom } from "../models/my-custom"
+import { Modules } from "@medusajs/framework/utils"
-type MyCustom = InferTypeOf
+// ...
-class HelloModuleService extends MedusaService({ MyCustom }) {
- async doSomething(): Promise {
- // ...
- }
-}
+await link.dismiss({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ "helloModuleService": {
+ my_custom_id: "mc_123",
+ },
+})
```
+The `dismiss` method accepts the same parameter type as the [create method](#create-link).
-# Data Model Default Properties
-
-In this chapter, you'll learn about the properties available by default in your data model.
-
-When you create a data model, the following properties are created for you by Medusa:
-
-- `created_at`: A `dateTime` property that stores when a record of the data model was created.
-- `updated_at`: A `dateTime` property that stores when a record of the data model was updated.
-- `deleted_at`: A `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.
+The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
+***
-# Data Model’s Primary Key
+## Cascade Delete Linked Records
-In this chapter, you’ll learn how to configure the primary key of a data model.
+If a record is deleted, use the `delete` method of Link to delete all linked records.
-## primaryKey Method
+For example:
-To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
+```ts
+import { Modules } from "@medusajs/framework/utils"
-For example:
+// ...
-```ts highlights={highlights}
-import { model } from "@medusajs/framework/utils"
+await productModuleService.deleteVariants([variant.id])
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- // ...
+await link.delete({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
})
-
-export default MyCustom
```
-In the example above, the `id` property is defined as the data model's primary key.
+This deletes all records linked to the deleted product.
+***
-# Data Model Property Types
+## Restore Linked Records
-In this chapter, you’ll learn about the types of properties in a data model’s schema.
+If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records.
-## id
+For example:
-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.
+```ts
+import { Modules } from "@medusajs/framework/utils"
-For example:
+// ...
-```ts highlights={idHighlights}
-import { model } from "@medusajs/framework/utils"
+await productModuleService.restoreProducts(["prod_123"])
-const MyCustom = model.define("my_custom", {
- id: model.id(),
- // ...
+await link.restore({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
})
-
-export default MyCustom
```
-***
-
-## text
-The `text` method defines a string property.
+# Query Context
-For example:
+In this chapter, you'll learn how to pass contexts when retrieving data with [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-```ts highlights={textHighlights}
-import { model } from "@medusajs/framework/utils"
+## What is Query Context?
-const MyCustom = model.define("my_custom", {
- name: model.text(),
- // ...
-})
+Query context is a way to pass additional information when retrieving data with Query. This data can be useful when applying custom transformations to the retrieved data based on the current context.
-export default MyCustom
-```
+For example, consider you have a Blog Module with posts and authors. You can accept the user's language as a context and return the posts in the user's language. Another example is how Medusa uses Query Context to [retrieve product variants' prices based on the customer's currency](https://docs.medusajs.com/resources/commerce-modules/product/guides/price/index.html.md).
***
-## number
+## How to Use Query Context
-The `number` method defines a number property.
+The `query.graph` method accepts an optional `context` parameter that can be used to pass additional context either to the data model you're retrieving (for example, `post`), or its related and linked models (for example, `author`).
-For example:
+You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
-```ts highlights={numberHighlights}
-import { model } from "@medusajs/framework/utils"
+For example, to retrieve posts using Query while passing the user's language as a context:
-const MyCustom = model.define("my_custom", {
- age: model.number(),
- // ...
+```ts
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ }),
})
-
-export default MyCustom
```
-***
+In this example, you pass in the context a `lang` property whose value is `es`.
-## float
+Then, to handle the context while retrieving records of the data model, in the associated module's service you override the generated `list` method of the data model.
-This property is only available after [Medusa v2.1.2](https://github.com/medusajs/medusa/releases/tag/v2.1.2).
+For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
-The `float` method defines a number property that allows for values with decimal places.
+```ts highlights={highlights2}
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
-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).
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
-For example:
+ let posts = await super.listPosts(filters, config, sharedContext)
-```ts highlights={floatHighlights}
-import { model } from "@medusajs/framework/utils"
+ if (context.lang === "es") {
+ posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
-const MyCustom = model.define("my_custom", {
- rating: model.float(),
- // ...
-})
+ return posts
+ }
+}
-export default MyCustom
+export default BlogModuleService
```
-***
-
-## bigNumber
-
-The `bigNumber` method defines a number property that expects large numbers, such as prices.
+In the above example, you override the generated `listPosts` method. This method receives as a first parameter the filters passed to the query, but it also includes a `context` property that holds the context passed to the query.
-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).
+You extract the context from `filters`, then retrieve the posts using the parent's `listPosts` method. After that, if the language is set in the context, you transform the titles of the posts.
-For example:
+All posts returned will now have their titles appended with "en español".
-```ts highlights={bigNumberHighlights}
-import { model } from "@medusajs/framework/utils"
+Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
-const MyCustom = model.define("my_custom", {
- price: model.bigNumber(),
- // ...
-})
+### Using Pagination with Query
-export default MyCustom
-```
+If you pass pagination fields to `query.graph`, you must also override the `listAndCount` method in the service.
-***
+For example, following along with the previous example, you must override the `listAndCountPosts` method of the Blog Module's service:
-## boolean
+```ts
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
-The `boolean` method defines a boolean property.
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listAndCountPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
-For example:
+ const result = await super.listAndCountPosts(
+ filters,
+ config,
+ sharedContext
+ )
-```ts highlights={booleanHighlights}
-import { model } from "@medusajs/framework/utils"
+ if (context.lang === "es") {
+ result.posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
-const MyCustom = model.define("my_custom", {
- hasAccount: model.boolean(),
- // ...
-})
+ return result
+ }
+}
-export default MyCustom
+export default BlogModuleService
```
+Now, the `listAndCountPosts` method will handle the context passed to `query.graph` when you pass pagination fields. You can also move the logic to transform the posts' titles to a separate method and call it from both `listPosts` and `listAndCountPosts`.
+
***
-### enum
+## Passing Query Context to Related Data Models
-The `enum` method defines a property whose value can only be one of the specified values.
+If you're retrieving a data model and you want to pass context to its associated model in the same module, you can pass them as part of `QueryContext`'s parameter, then handle them in the same `list` method.
-For example:
+For linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
-```ts highlights={enumHighlights}
-import { model } from "@medusajs/framework/utils"
+For example, to pass a context for the post's authors:
-const MyCustom = model.define("my_custom", {
- color: model.enum(["black", "white"]),
- // ...
+```ts highlights={highlights3}
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ author: QueryContext({
+ lang: "es",
+ }),
+ }),
})
-
-export default MyCustom
```
-The `enum` method accepts an array of possible string values.
+Then, in the `listPosts` method, you can handle the context for the post's authors:
-***
+```ts highlights={highlights4}
+import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
+import { Context, FindConfig } from "@medusajs/framework/types"
+import Post from "./models/post"
+import Author from "./models/author"
-## dateTime
+class BlogModuleService extends MedusaService({
+ Post,
+ Author,
+}){
+ // @ts-ignore
+ async listPosts(
+ filters?: any,
+ config?: FindConfig | undefined,
+ @MedusaContext() sharedContext?: Context | undefined
+ ) {
+ const context = filters.context ?? {}
+ delete filters.context
-The `dateTime` method defines a timestamp property.
+ let posts = await super.listPosts(filters, config, sharedContext)
-For example:
+ const isPostLangEs = context.lang === "es"
+ const isAuthorLangEs = context.author?.lang === "es"
-```ts highlights={dateTimeHighlights}
-import { model } from "@medusajs/framework/utils"
+ if (isPostLangEs || isAuthorLangEs) {
+ posts = posts.map((post) => {
+ return {
+ ...post,
+ title: isPostLangEs ? post.title + " en español" : post.title,
+ author: {
+ ...post.author,
+ name: isAuthorLangEs ? post.author.name + " en español" : post.author.name,
+ },
+ }
+ })
+ }
-const MyCustom = model.define("my_custom", {
- date_of_birth: model.dateTime(),
- // ...
-})
+ return posts
+ }
+}
-export default MyCustom
+export default BlogModuleService
```
-***
+The context in `filters` will also have the context for `author`, which you can use to make transformations to the post's authors.
-## json
+***
-The `json` method defines a property whose value is a stringified JSON object.
+## Passing Query Context to Linked Data Models
-For example:
+If you're retrieving a data model and you want to pass context to a linked model in a different module, pass to the `context` property an object instead, where its keys are the linked model's name and the values are the context for that linked model.
-```ts highlights={jsonHighlights}
-import { model } from "@medusajs/framework/utils"
+For example, consider the Product Module's `Product` data model is linked to the Blog Module's `Post` data model. You can pass context to the `Post` data model while retrieving products like so:
-const MyCustom = model.define("my_custom", {
- metadata: model.json(),
- // ...
+```ts highlights={highlights5}
+const { data } = await query.graph({
+ entity: "product",
+ fields: ["*", "post.*"],
+ context: {
+ post: QueryContext({
+ lang: "es",
+ }),
+ },
})
-
-export default MyCustom
```
-***
+In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
-## array
+To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
-The `array` method defines an array of strings property.
-For example:
+# Module Link Direction
-```ts highlights={arrHightlights}
-import { model } from "@medusajs/framework/utils"
+In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
-const MyCustom = model.define("my_custom", {
- names: model.array(),
- // ...
-})
+## Link Direction
-export default MyCustom
+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 `helloModuleService`'s `myCustom` data model to the Product Module's `product` data model:
+
+```ts
+export default defineLink(
+ HelloModule.linkable.myCustom,
+ ProductModule.linkable.product
+)
```
-***
+Whereas the following defines a link from the Product Module's `product` data model to the `helloModuleService`'s `myCustom` data model:
-## Properties Reference
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.myCustom
+)
+```
-Refer to the [Data Model API reference](https://docs.medusajs.com/resources/references/data-model/index.html.md) for a full reference of the properties.
+The above links are two different links that serve different purposes.
+***
-# Manage Relationships
+## Which Link Direction to Use?
-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.
+### Extend Data Models
-## Manage One-to-One Relationship
+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.
-### BelongsTo Side of One-to-One
+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:
-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.
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.subtitle
+)
+```
-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:
+### Associate Data Models
-```ts highlights={belongsHighlights}
-// when creating an email
-const email = await helloModuleService.createEmails({
- // other properties...
- user_id: "123",
-})
+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.
-// when updating an email
-const email = await helloModuleService.updateEmails({
- id: "321",
- // other properties...
- user_id: "123",
-})
-```
+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`:
-In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to.
+```ts
+export default defineLink(
+ HelloModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
-### 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.
+# Query
-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:
+In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
-```ts highlights={hasOneHighlights}
-// when creating a user
-const user = await helloModuleService.createUsers({
- // other properties...
- email: "123",
-})
+## What is Query?
-// when updating a user
-const user = await helloModuleService.updateUsers({
- id: "321",
- // other properties...
- email: "123",
-})
-```
+Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
-In the example above, you pass the `email` property when creating or updating a user to specify the email it has.
+In your resources, such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
***
-## Manage One-to-Many Relationship
+## Query Example
-In a one-to-many relationship, you can only manage the associations from the `belongsTo` side.
+For example, create the route `src/api/query/route.ts` with the following content:
-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.
+```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"
-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:
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
-```ts highlights={manyBelongsHighlights}
-// when creating a product
-const product = await helloModuleService.createProducts({
- // other properties...
- store_id: "123",
-})
+ const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ fields: ["id", "name"],
+ })
-// when updating a product
-const product = await helloModuleService.updateProducts({
- id: "321",
- // other properties...
- store_id: "123",
-})
+ res.json({ my_customs: myCustoms })
+}
```
-In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to.
+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:
-## Manage Many-to-Many Relationship
+- `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.
-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.
+The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
-### Create Associations
+```json title="Returned Data"
+{
+ "data": [
+ {
+ "id": "123",
+ "name": "test"
+ }
+ ]
+}
+```
-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"],
-})
+## Querying the Graph
-// when creating an order
-const order = await helloModuleService.createOrders({
- id: "321",
- // other properties...
- products: ["123", "321"],
-})
-```
+When you use the `query.graph` method, you're running a query through an internal graph that the Medusa application creates.
-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.
+This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.
-### 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.
+## Retrieve Linked Records
-However, this removes any existing associations to records whose IDs aren't included in the array.
+Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
-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:
+For example:
-```ts
-const product = await helloModuleService.updateProducts({
- id: "123",
- // other properties...
- orders: ["321"],
+```ts highlights={[["6"]]}
+const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ fields: [
+ "id",
+ "name",
+ "product.*",
+ ],
})
```
-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.
+`.*` means that all of data model's properties should be retrieved. To retrieve a specific property, replace the `*` with the property's name. For example, `product.title`.
-So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product:
+### Retrieve List Link Records
-```ts highlights={updateAssociationHighlights}
-const product = await helloModuleService.retrieveProduct(
- "123",
- {
- relations: ["orders"],
- }
-)
+If the linked data model has `isList` enabled in the link definition, pass in `fields` the data model's plural name suffixed with `.*`.
-const updatedProduct = await helloModuleService.updateProducts({
- id: product.id,
- // other properties...
- orders: [
- ...product.orders.map((order) => order.id),
- "321",
+For example:
+
+```ts highlights={[["6"]]}
+const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ fields: [
+ "id",
+ "name",
+ "products.*",
],
})
```
-This keeps existing associations between the product and orders, and adds a new one.
+### Apply Filters and Pagination on Linked Records
-***
+Consider you want to apply filters or pagination configurations on the product(s) linked to `my_custom`. To do that, you must query the module link's table instead.
-## Manage Many-to-Many Relationship with pivotEntity
+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.
-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.
+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.
-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:
-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={queryLinkTableHighlights}
+import productCustomLink from "../../../links/product-custom"
-```ts highlights={["4"]}
-class HelloModuleService extends MedusaService({
- Order,
- Product,
- OrderProduct,
-}) {}
+// ...
+
+const { data: productCustoms } = await query.graph({
+ entity: productCustomLink.entryPoint,
+ fields: ["*", "product.*", "my_custom.*"],
+ pagination: {
+ take: 5,
+ skip: 0,
+ },
+})
```
-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.
+In the object passed to the `graph` method:
-For example:
+- 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 `MyCustom` record.
+ - `my_custom.*` to retrieve the fields of a `MyCustom` record linked to a product record.
-```ts
-// create order-product association
-const orderProduct = await helloModuleService.createOrderProducts({
- order_id: "123",
- product_id: "123",
- metadata: {
- test: true,
+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",
+ "my_custom_id": "123",
+ "product": {
+ "id": "prod_123",
+ // other product fields...
},
-})
+ "my_custom": {
+ "id": "123",
+ // other my_custom fields...
+ }
+}]
+```
-// update order-product association
-const orderProduct = await helloModuleService.updateOrderProducts({
- id: "123",
- metadata: {
- test: false,
+***
+
+## Apply Filters
+
+```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
+const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ fields: ["id", "name"],
+ filters: {
+ id: [
+ "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
+ "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
+ ],
},
})
-
-// delete order-product association
-await helloModuleService.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`.
+The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
-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.
+In the example above, you filter the `my_custom` records by multiple IDs.
-***
+Filters don't apply on fields of linked data models from other modules.
-## 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.
+## Apply Pagination
-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.
+```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
+const {
+ data: myCustoms,
+ metadata: { count, take, skip } = {},
+} = await query.graph({
+ entity: "my_custom",
+ fields: ["id", "name"],
+ pagination: {
+ skip: 0,
+ take: 10,
+ },
+})
+```
-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:
+The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
-```ts highlights={retrieveHighlights}
-const product = await helloModuleService.retrieveProducts(
- "123",
- {
- relations: ["orders"],
- }
-)
-```
+To paginate the returned records, pass the following properties to `pagination`:
-In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
+- `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:
-# Data Model Relationships
+- skip: (\`number\`) The number of records skipped.
+- take: (\`number\`) The number of records requested to fetch.
+- count: (\`number\`) The total number of records.
-In this chapter, you’ll learn how to define relationships between data models in your module.
+### Sort Records
-## What is a Relationship Property?
+```ts highlights={[["5"], ["6"], ["7"]]}
+const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ fields: ["id", "name"],
+ pagination: {
+ order: {
+ name: "DESC",
+ },
+ },
+})
+```
-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`.
+Sorting doesn't work on fields of linked data models from other modules.
-When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type.
+To sort returned records, pass an `order` property to `pagination`.
-You want to create a relation between data models in the same module.
+The `order` property is an object whose keys are property names, and values are either:
-You want to create a relationship between data models in different modules. Use module links instead.
+- `ASC` to sort records by that property in ascending order.
+- `DESC` to sort records by that property in descending order.
***
-## One-to-One Relationship
+## Request Query Configurations
-A one-to-one relationship indicates that one record of a data model belongs to or is associated with another.
+For API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
-To define a one-to-one relationship, create relationship properties in the data models using the following methods:
+- 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.
-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.
+Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
-For example:
+### Step 1: Add Middleware
-```ts highlights={oneToOneHighlights}
-import { model } from "@medusajs/framework/utils"
+The first step is to use the `validateAndTransformQuery` middleware on the `GET` route. You add the middleware in `src/api/middlewares.ts`:
-const User = model.define("user", {
- id: model.id().primaryKey(),
- email: model.hasOne(() => Email),
-})
+```ts title="src/api/middlewares.ts"
+import {
+ validateAndTransformQuery,
+ defineMiddlewares,
+} from "@medusajs/framework/http"
+import { createFindParams } from "@medusajs/medusa/api/utils/validators"
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- user: model.belongsTo(() => User, {
- mappedBy: "email",
- }),
+export const GetCustomSchema = createFindParams()
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/customs",
+ method: "GET",
+ middlewares: [
+ validateAndTransformQuery(
+ GetCustomSchema,
+ {
+ defaults: [
+ "id",
+ "name",
+ "products.*",
+ ],
+ isList: true,
+ }
+ ),
+ ],
+ },
+ ],
})
```
-In the example above, a user has one email, and an email belongs to one user.
+The `validateAndTransformQuery` accepts two parameters:
-The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model.
+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`.
-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.
+### Step 2: Use Configurations in API Route
-### Optional Relationship
+After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
-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/configure-properties#nullable-property/index.html.md).
+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.
-### One-sided One-to-One Relationship
+As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), `remoteQueryConfig` has been depercated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
-If the one-to-one relationship is only defined on one side, pass `undefined` to the `mappedBy` property in the `belongsTo` method.
+For example, Create the file `src/api/customs/route.ts` with the following content:
-For example:
+```ts title="src/api/customs/route.ts"
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
-```ts highlights={oneToOneUndefinedHighlights}
-import { model } from "@medusajs/framework/utils"
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
-const User = model.define("user", {
- id: model.id().primaryKey(),
-})
+ const { data: myCustoms } = await query.graph({
+ entity: "my_custom",
+ ...req.queryConfig,
+ })
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- user: model.belongsTo(() => User, {
- mappedBy: undefined,
- }),
-})
+ res.json({ my_customs: myCustoms })
+}
```
-### 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.
-
-
+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.
-## One-to-Many Relationship
+### Test it Out
-A one-to-many relationship indicates that one record of a data model has many records of another data model.
+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.
-To define a one-to-many relationship, create relationship properties in the data models using the following methods:
+```json title="Returned Data"
+{
+ "my_customs": [
+ {
+ "id": "123",
+ "name": "test"
+ }
+ ]
+}
+```
-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.
+Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
-For example:
+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.
-```ts highlights={oneToManyHighlights}
-import { model } from "@medusajs/framework/utils"
-const Store = model.define("store", {
- id: model.id().primaryKey(),
- products: model.hasMany(() => Product),
-})
+# Add Data Model Check Constraints
-const Product = model.define("product", {
- id: model.id().primaryKey(),
- store: model.belongsTo(() => Store, {
- mappedBy: "products",
- }),
-})
-```
+In this chapter, you'll learn how to add check constraints to your data model.
-In this example, a store has many products, but a product belongs to one store.
+## What is a Check Constraint?
-### Optional Relationship
+A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
-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/configure-properties#nullable-property/index.html.md).
+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.
-### 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:
+## How to Set a Check Constraint?
-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.
+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"
-## Many-to-Many Relationship
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ (columns) => `${columns.price} >= 0`,
+])
+```
-A many-to-many relationship indicates that many records of a data model can be associated with many records of another data model.
+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.
-To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
+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.
-For example:
+You can also pass an object to the `checks` method:
-```ts highlights={manyToManyHighlights}
+```ts highlights={checks2Highlights}
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",
- }),
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
})
+.checks([
+ {
+ name: "custom_product_price_check",
+ expression: (columns) => `${columns.price} >= 0`,
+ },
+])
```
-The `manyToMany` method accepts two parameters:
+The object accepts the following properties:
-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.
+- `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).
-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.
+## Apply in Migrations
-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.
+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.
-### Many-to-Many Relationship in the Database
+To generate a migration for the data model's module then reflect it on the database, run the following command:
-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`.
+```bash
+npx medusa db:generate custom_module
+npx medusa db:migrate
+```
-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 first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
-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`.
+# Configure Data Model Properties
-
+In this chapter, you’ll learn how to configure data model properties.
-### Many-To-Many with Custom Columns
+## Property’s Default Value
-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.
+Use the `default` method on a property's definition to specify the default value of a property.
For example:
-```ts highlights={manyToManyColumnHighlights}
+```ts highlights={defaultHighlights}
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),
+const MyCustom = model.define("my_custom", {
+ color: model
+ .enum(["black", "white"])
+ .default("black"),
+ age: model
+ .number()
+ .default(0),
+ // ...
})
-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(),
-})
+export default MyCustom
```
-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.
+In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
***
-## 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.
+## Nullable Property
-As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
+Use the `nullable` method to indicate that a property’s value can be `null`.
For example:
-```ts highlights={relationNameHighlights}
+```ts highlights={nullableHighlights}
import { model } from "@medusajs/framework/utils"
-const User = model.define("user", {
- id: model.id().primaryKey(),
- email: model.hasOne(() => Email, {
- mappedBy: "owner",
- }),
+const MyCustom = model.define("my_custom", {
+ price: model.bigNumber().nullable(),
+ // ...
})
-const Email = model.define("email", {
- id: model.id().primaryKey(),
- owner: model.belongsTo(() => User, {
- mappedBy: "email",
- }),
-})
+export default MyCustom
```
-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.
+## Unique Property
-The `cascades` method used on a data model configures which child records an operation is cascaded to.
+The `unique` method indicates that a property’s value must be unique in the database through a unique index.
For example:
-```ts highlights={highlights}
+```ts highlights={uniqueHighlights}
import { model } from "@medusajs/framework/utils"
-const Store = model.define("store", {
- id: model.id().primaryKey(),
- products: model.hasMany(() => Product),
-})
-.cascades({
- delete: ["products"],
+const User = model.define("user", {
+ email: model.text().unique(),
+ // ...
})
-const Product = model.define("product", {
- id: model.id().primaryKey(),
- store: model.belongsTo(() => Store, {
- mappedBy: "products",
- }),
-})
+export default User
```
-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 this example, multiple users can’t have the same email.
-In the example above, when a store is deleted, its associated products are also deleted.
+# Data Model Default Properties
-# Write Migration
+In this chapter, you'll learn about the properties available by default in your data model.
-In this chapter, you'll learn how to create a migration and write it manually.
+When you create a data model, the following properties are created for you by Medusa:
-## What is a Migration?
+- `created_at`: A `dateTime` property that stores when a record of the data model was created.
+- `updated_at`: A `dateTime` property that stores when a record of the data model was updated.
+- `deleted_at`: A `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.
-A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
-- The `up` method reflects changes on the database.
-- The `down` method reverts the changes made in the `up` method.
+# Infer Type of Data Model
-***
+In this chapter, you'll learn how to infer the type of a data model.
-## How to Write a Migration?
+## How to Infer Type of Data Model?
-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 the specified modules' data models.
+Consider you have a `MyCustom` 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.
-Alternatively, you can manually create a migration file under the `migrations` directory of your module.
+Instead, Medusa provides `InferTypeOf` that transforms your data model to a type.
For example:
-```ts title="src/modules/hello/migrations/Migration20240429.ts"
-import { Migration } from "@mikro-orm/migrations"
+```ts
+import { InferTypeOf } from "@medusajs/framework/types"
+import { MyCustom } from "../models/my-custom" // relative path to the model
-export class Migration20240702105919 extends Migration {
+export type MyCustom = InferTypeOf
+```
- async up(): Promise {
- this.addSql("create table if not exists \"my_custom\" (\"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 \"my_custom_pkey\" primary key (\"id\"));")
- }
+`InferTypeOf` accepts as a type argument the type of the data model.
- async down(): Promise {
- this.addSql("drop table if exists \"my_custom\" cascade;")
- }
+Since the `MyCustom` 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 `MyCustom` 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 { MyCustom } from "../models/my-custom"
+
+type MyCustom = InferTypeOf
+class HelloModuleService extends MedusaService({ MyCustom }) {
+ async doSomething(): Promise {
+ // ...
+ }
}
```
-The migration's file name should be of the format `Migration{YEAR}{MONTH}{DAY}.ts`. 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.
+# Data Model Database Index
-In the example above, the `up` method creates the table `my_custom`, and the `down` method drops the table if the migration is reverted.
+In this chapter, you’ll learn how to define a database index on a data model.
-Refer to [MikroORM's documentation](https://mikro-orm.io/docs/migrations#migration-class) for more details on writing migrations.
+## Define Database Index on Property
-***
+Use the `index` method on a property's definition to define a database index.
-## Run the Migration
+For example:
-To run your migration, run the following command:
+```ts highlights={highlights}
+import { model } from "@medusajs/framework/utils"
-This command also syncs module links. If you don't want that, use the `--skip-links` option.
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text().index(
+ "IDX_MY_CUSTOM_NAME"
+ ),
+})
-```bash
-npx medusa db:migrate
+export default MyCustom
```
-This reflects the changes in the database as implemented in the migration's `up` method.
+The `index` method optionally accepts the name of the index as a parameter.
-***
+In this example, you define an index on the `name` property.
-## Rollback the Migration
+***
-To rollback or revert the last migration you ran for a module, run the following command:
+## Define Database Index on Data Model
-```bash
-npx medusa db:rollback helloModuleService
-```
+A data model has an `indexes` method that defines database indices on its properties.
-This rolls back the last ran migration on the Hello Module.
+The index can be on multiple columns (composite index). For example:
-***
+```ts highlights={dataModelIndexHighlights}
+import { model } from "@medusajs/framework/utils"
-## More Database Commands
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ },
+])
-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).
+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.
-# Searchable Data Model Property
+In the above example, you define a composite index on the `name` and `age` properties.
-In this chapter, you'll learn what a searchable property is and how to define it.
+### Index Conditions
-## What is a Searchable Property?
+An index can have conditions. For example:
-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.
+```ts highlights={conditionHighlights}
+import { model } from "@medusajs/framework/utils"
-When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
+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
+```
-## Define a Searchable Property
+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.
-Use the `searchable` method on a `text` property to indicate that it's searchable.
+In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
-For example:
+A property's condition can be a negation. For example:
-```ts highlights={searchableHighlights}
+```ts highlights={negationHighlights}
import { model } from "@medusajs/framework/utils"
const MyCustom = model.define("my_custom", {
- name: model.text().searchable(),
- // ...
-})
+ id: model.id().primaryKey(),
+ name: model.text(),
+ age: model.number().nullable(),
+}).indexes([
+ {
+ on: ["name", "age"],
+ where: {
+ age: {
+ $ne: null,
+ },
+ },
+ },
+])
export default MyCustom
```
-In this example, the `name` property is searchable.
+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.
-### Search Example
+In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
-If you pass a `q` filter to the `listMyCustoms` method:
+### Unique Database Index
-```ts
-const myCustoms = await helloModuleService.listMyCustoms({
- q: "John",
-})
+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 retrieves records that include `John` in their `name` property.
+This creates a unique composite index on the `name` and `age` properties.
-# Add Columns to a Link
+# Manage Relationships
-In this chapter, you'll learn how to add custom columns to a link definition and manage them.
+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.
-## How to Add Custom Columns to a Link's Table?
+## Manage One-to-One Relationship
-The `defineLink` function used to define a link accepts a third parameter, which is an object of options.
+### BelongsTo Side of One-to-One
-To add custom columns to a link's table, pass in the third parameter of `defineLink` a `database` property:
+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.
-```ts highlights={linkHighlights}
-import HelloModule from "../modules/hello"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
+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:
-export default defineLink(
- ProductModule.linkable.product,
- HelloModule.linkable.myCustom,
- {
- database: {
- extraColumns: {
- metadata: {
- type: "json",
- },
- },
- },
- }
-)
-```
+```ts highlights={belongsHighlights}
+// when creating an email
+const email = await helloModuleService.createEmails({
+ // other properties...
+ user_id: "123",
+})
-This adds to the table created for the link between `product` and `myCustom` a `metadata` column of type `json`.
+// when updating an email
+const email = await helloModuleService.updateEmails({
+ id: "321",
+ // other properties...
+ user_id: "123",
+})
+```
-### Database Options
+In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to.
-The `database` property defines configuration for the table created in the database.
+### HasOne Side
-Its `extraColumns` property defines custom columns to create in the link's table.
+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.
-`extraColumns`'s value is an object whose keys are the names of the columns, and values are the column's configurations as an object.
+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:
-### Column Configurations
+```ts highlights={hasOneHighlights}
+// when creating a user
+const user = await helloModuleService.createUsers({
+ // other properties...
+ email: "123",
+})
-The column's configurations object accepts the following properties:
+// when updating a user
+const user = await helloModuleService.updateUsers({
+ id: "321",
+ // other properties...
+ email: "123",
+})
+```
-- `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.
+In the example above, you pass the `email` property when creating or updating a user to specify the email it has.
***
-## Set Custom Column when Creating Link
+## Manage One-to-Many Relationship
-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.
+In a one-to-many relationship, you can only manage the associations from the `belongsTo` side.
-For example:
+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.
-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).
+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
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- HELLO_MODULE: {
- my_custom_id: "321",
- },
- data: {
- metadata: {
- test: true,
- },
- },
+```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.
+
***
-## Retrieve Custom Column with Link
+## Manage Many-to-Many Relationship
-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.
+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.
-For example:
+### Create Associations
-```ts highlights={retrieveHighlights}
-import productHelloLink from "../links/product-hello"
+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:
-const { data } = await query.graph({
- entity: productHelloLink.entryPoint,
- fields: ["metadata", "product.*", "my_custom.*"],
- filters: {
- product_id: "prod_123",
- },
+```ts highlights={manyHighlights}
+// when creating a product
+const product = await helloModuleService.createProducts({
+ // other properties...
+ orders: ["123", "321"],
})
-```
-
-This retrieves the product of id `prod_123` and its linked `my_custom` records.
-In the `fields` array you pass `metadata`, which is the custom column to retrieve of the link.
+// 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 Custom Column's Value
+### Update Associations
-Link's `create` method updates a link's data if the link between the specified records already exists.
+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.
-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.
+However, this removes any existing associations to records whose IDs aren't included in the array.
-For example:
+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
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "123",
- },
- HELLO_MODULE: {
- my_custom_id: "321",
- },
- data: {
- metadata: {
- test: false,
- },
- },
+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.
-# Query
-
-In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
+So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product:
-## What is Query?
+```ts highlights={updateAssociationHighlights}
+const product = await helloModuleService.retrieveProduct(
+ "123",
+ {
+ relations: ["orders"],
+ }
+)
-Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key.
+const updatedProduct = await helloModuleService.updateProducts({
+ id: product.id,
+ // other properties...
+ orders: [
+ ...product.orders.map((order) => order.id),
+ "321",
+ ],
+})
+```
-In your resources, such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.
+This keeps existing associations between the product and orders, and adds a new one.
***
-## Query Example
-
-For example, create the route `src/api/query/route.ts` with the following content:
+## Manage Many-to-Many Relationship with pivotEntity
-```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"
+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.
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+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.
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- })
+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:
- res.json({ my_customs: myCustoms })
-}
+```ts highlights={["4"]}
+class HelloModuleService extends MedusaService({
+ Order,
+ Product,
+ OrderProduct,
+}) {}
```
-In the above example, you resolve Query from the Medusa container using the `ContainerRegistrationKeys.QUERY` (`query`) key.
+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.
-Then, you run a query using its `graph` method. This method accepts as a parameter an object with the following required properties:
+For example:
-- `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.
+```ts
+// create order-product association
+const orderProduct = await helloModuleService.createOrderProducts({
+ order_id: "123",
+ product_id: "123",
+ metadata: {
+ test: true,
+ },
+})
-The method returns an object that has a `data` property, which holds an array of the retrieved data. For example:
+// update order-product association
+const orderProduct = await helloModuleService.updateOrderProducts({
+ id: "123",
+ metadata: {
+ test: false,
+ },
+})
-```json title="Returned Data"
-{
- "data": [
- {
- "id": "123",
- "name": "test"
- }
- ]
-}
+// delete order-product association
+await helloModuleService.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`.
-## Querying the Graph
+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.
-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 Records of Relation
-## Retrieve Linked Records
+The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options.
-Retrieve the records of a linked data model by passing in `fields` the data model's name suffixed with `.*`.
+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:
+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={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: [
- "id",
- "name",
- "product.*",
- ],
-})
+```ts highlights={retrieveHighlights}
+const product = await helloModuleService.retrieveProducts(
+ "123",
+ {
+ relations: ["orders"],
+ }
+)
```
-`.*` means that all of data model's properties should be retrieved. To retrieve a specific property, replace the `*` with the property's name. For example, `product.title`.
-
-### 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:
+In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
-```ts highlights={[["6"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: [
- "id",
- "name",
- "products.*",
- ],
-})
-```
-### Apply Filters and Pagination on Linked Records
+# Data Model’s Primary Key
-Consider you want to apply filters or pagination configurations on the product(s) linked to `my_custom`. To do that, you must query the module link's table instead.
+In this chapter, you’ll learn how to configure the primary key of a data model.
-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.
+## primaryKey Method
-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.
+To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
For example:
-```ts highlights={queryLinkTableHighlights}
-import productCustomLink from "../../../links/product-custom"
-
-// ...
+```ts highlights={highlights}
+import { model } from "@medusajs/framework/utils"
-const { data: productCustoms } = await query.graph({
- entity: productCustomLink.entryPoint,
- fields: ["*", "product.*", "my_custom.*"],
- pagination: {
- take: 5,
- skip: 0,
- },
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ // ...
})
+
+export default MyCustom
```
-In the object passed to the `graph` method:
+In the example above, the `id` property is defined as the data model's primary key.
-- 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 `MyCustom` record.
- - `my_custom.*` to retrieve the fields of a `MyCustom` record linked to a product record.
-You can then apply any [filters](#apply-filters) or [pagination configurations](#apply-pagination).
+# Data Model Property Types
-The returned `data` is similar to the following:
+In this chapter, you’ll learn about the types of properties in a data model’s schema.
-```json title="Example Result"
-[{
- "id": "123",
- "product_id": "prod_123",
- "my_custom_id": "123",
- "product": {
- "id": "prod_123",
- // other product fields...
- },
- "my_custom": {
- "id": "123",
- // other my_custom fields...
- }
-}]
-```
+## 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.
-## Apply Filters
+For example:
-```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- filters: {
- id: [
- "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
- "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
- ],
- },
+```ts highlights={idHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id(),
+ // ...
})
+
+export default MyCustom
```
-The `query.graph` function accepts a `filters` property. You can use this property to filter retrieved records.
+***
-In the example above, you filter the `my_custom` records by multiple IDs.
+## text
-Filters don't apply on fields of linked data models from other modules.
+The `text` method defines a string property.
-***
+For example:
-## Apply Pagination
+```ts highlights={textHighlights}
+import { model } from "@medusajs/framework/utils"
-```ts highlights={[["8", "skip", "The number of records to skip before fetching the results."], ["9", "take", "The number of records to fetch."]]}
-const {
- data: myCustoms,
- metadata: { count, take, skip } = {},
-} = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- pagination: {
- skip: 0,
- take: 10,
- },
+const MyCustom = model.define("my_custom", {
+ name: model.text(),
+ // ...
})
-```
-The `graph` method's object parameter accepts a `pagination` property to configure the pagination of retrieved records.
+export default MyCustom
+```
-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.
+## number
-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:
+The `number` method defines a number property.
-- skip: (\`number\`) The number of records skipped.
-- take: (\`number\`) The number of records requested to fetch.
-- count: (\`number\`) The total number of records.
+For example:
-### Sort Records
+```ts highlights={numberHighlights}
+import { model } from "@medusajs/framework/utils"
-```ts highlights={[["5"], ["6"], ["7"]]}
-const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- fields: ["id", "name"],
- pagination: {
- order: {
- name: "DESC",
- },
- },
+const MyCustom = model.define("my_custom", {
+ age: model.number(),
+ // ...
})
-```
-Sorting doesn't work on fields of linked data models from other modules.
+export default MyCustom
+```
-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:
+## float
-- `ASC` to sort records by that property in ascending order.
-- `DESC` to sort records by that property in descending order.
+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.
-## Request Query Configurations
+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 API routes that retrieve a single or list of resources, Medusa provides a `validateAndTransformQuery` middleware that:
+For example:
-- 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.
+```ts highlights={floatHighlights}
+import { model } from "@medusajs/framework/utils"
-Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.
+const MyCustom = model.define("my_custom", {
+ rating: model.float(),
+ // ...
+})
-### Step 1: Add Middleware
+export default MyCustom
+```
-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"
+## bigNumber
-export const GetCustomSchema = createFindParams()
+The `bigNumber` method defines a number property that expects large numbers, such as prices.
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/customs",
- method: "GET",
- middlewares: [
- validateAndTransformQuery(
- GetCustomSchema,
- {
- defaults: [
- "id",
- "name",
- "products.*",
- ],
- isList: true,
- }
- ),
- ],
- },
- ],
-})
-```
+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).
-The `validateAndTransformQuery` accepts two parameters:
+For example:
-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`.
+```ts highlights={bigNumberHighlights}
+import { model } from "@medusajs/framework/utils"
-### Step 2: Use Configurations in API Route
+const MyCustom = model.define("my_custom", {
+ price: model.bigNumber(),
+ // ...
+})
-After applying this middleware, your API route now accepts the `fields`, `offset`, `limit`, and `order` query parameters mentioned above.
+export default MyCustom
+```
-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 depercated in favor of `queryConfig`. Their usage is still the same, only the property name has changed.
+## boolean
-For example, Create the file `src/api/customs/route.ts` with the following content:
+The `boolean` method defines a boolean property.
-```ts title="src/api/customs/route.ts"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
+For example:
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
+```ts highlights={booleanHighlights}
+import { model } from "@medusajs/framework/utils"
- const { data: myCustoms } = await query.graph({
- entity: "my_custom",
- ...req.queryConfig,
- })
+const MyCustom = model.define("my_custom", {
+ hasAccount: model.boolean(),
+ // ...
+})
- res.json({ my_customs: myCustoms })
-}
+export default MyCustom
```
-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.
+### enum
-```json title="Returned Data"
-{
- "my_customs": [
- {
- "id": "123",
- "name": "test"
- }
- ]
-}
-```
+The `enum` method defines a property whose value can only be one of the specified values.
-Try passing one of the Query configuration parameters, like `fields` or `limit`, and you'll see its impact on the returned result.
+For example:
-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.
+```ts highlights={enumHighlights}
+import { model } from "@medusajs/framework/utils"
+const MyCustom = model.define("my_custom", {
+ color: model.enum(["black", "white"]),
+ // ...
+})
-# Link
+export default MyCustom
+```
-In this chapter, you’ll learn what Link is and how to use it to manage links.
+The `enum` method accepts an array of possible string values.
-As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below.
+***
-## What is Link?
+## dateTime
-Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name.
+The `dateTime` method defines a timestamp property.
For example:
-```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
+```ts highlights={dateTimeHighlights}
+import { model } from "@medusajs/framework/utils"
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-): Promise {
- const link = req.scope.resolve(
- ContainerRegistrationKeys.LINK
- )
-
+const MyCustom = model.define("my_custom", {
+ date_of_birth: model.dateTime(),
// ...
-}
-```
+})
-You can use its methods to manage links, such as create or delete links.
+export default MyCustom
+```
***
-## Create Link
+## json
-To create a link between records of two data models, use the `create` method of Link.
+The `json` method defines a property whose value is a stringified JSON object.
For example:
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
+```ts highlights={jsonHighlights}
+import { model } from "@medusajs/framework/utils"
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- "helloModuleService": {
- my_custom_id: "mc_123",
- },
+const MyCustom = model.define("my_custom", {
+ metadata: model.json(),
+ // ...
})
-```
-
-The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
-
-The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
-
-The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record.
-So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module.
+export default MyCustom
+```
***
-## Dismiss Link
+## array
-To remove a link between records of two data models, use the `dismiss` method of Link.
+The `array` method defines an array of strings property.
For example:
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
+```ts highlights={arrHightlights}
+import { model } from "@medusajs/framework/utils"
-await link.dismiss({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- "helloModuleService": {
- my_custom_id: "mc_123",
- },
+const MyCustom = model.define("my_custom", {
+ names: model.array(),
+ // ...
})
-```
-The `dismiss` method accepts the same parameter type as the [create method](#create-link).
-
-The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition.
+export default MyCustom
+```
***
-## Cascade Delete Linked Records
+## Properties Reference
-If a record is deleted, use the `delete` method of Link to delete all linked records.
+Refer to the [Data Model API reference](https://docs.medusajs.com/resources/references/data-model/index.html.md) for a full reference of the properties.
-For example:
-```ts
-import { Modules } from "@medusajs/framework/utils"
+# Searchable Data Model Property
-// ...
+In this chapter, you'll learn what a searchable property is and how to define it.
-await productModuleService.deleteVariants([variant.id])
+## What is a Searchable Property?
-await link.delete({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
-})
-```
+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.
-This deletes all records linked to the deleted product.
+When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
***
-## Restore Linked Records
+## Define a Searchable Property
-If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records.
+Use the `searchable` method on a `text` property to indicate that it's searchable.
For example:
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await productModuleService.restoreProducts(["prod_123"])
+```ts highlights={searchableHighlights}
+import { model } from "@medusajs/framework/utils"
-await link.restore({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
+const MyCustom = model.define("my_custom", {
+ name: model.text().searchable(),
+ // ...
})
+
+export default MyCustom
```
+In this example, the `name` property is searchable.
-# Module Link Direction
+### Search Example
-In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
+If you pass a `q` filter to the `listMyCustoms` method:
-## Link Direction
+```ts
+const myCustoms = await helloModuleService.listMyCustoms({
+ q: "John",
+})
+```
-The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
+This retrieves records that include `John` in their `name` property.
-For example, the following defines a link from the `helloModuleService`'s `myCustom` data model to the Product Module's `product` data model:
-```ts
-export default defineLink(
- HelloModule.linkable.myCustom,
- ProductModule.linkable.product
-)
-```
+# Data Model Relationships
-Whereas the following defines a link from the Product Module's `product` data model to the `helloModuleService`'s `myCustom` data model:
+In this chapter, you’ll learn how to define relationships between data models in your module.
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- HelloModule.linkable.myCustom
-)
-```
+## What is a Relationship Property?
-The above links are two different links that serve different purposes.
+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.
-## Which Link Direction to Use?
+You want to create a relation between data models in the same module.
-### Extend Data Models
+You want to create a relationship between data models in different modules. Use module links instead.
-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:
+## One-to-One Relationship
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- HelloModule.linkable.subtitle
-)
-```
+A one-to-one relationship indicates that one record of a data model belongs to or is associated with another.
-### Associate Data Models
+To define a one-to-one relationship, create relationship properties in the data models using the following methods:
-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.
+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, 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`:
+For example:
-```ts
-export default defineLink(
- HelloModule.linkable.post,
- ProductModule.linkable.product
-)
+```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.
-# Query Context
+The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model.
-In this chapter, you'll learn how to pass contexts when retrieving data with [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+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.
-## What is Query Context?
+### Optional Relationship
-Query context is a way to pass additional information when retrieving data with Query. This data can be useful when applying custom transformations to the retrieved data based on the current context.
+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/configure-properties#nullable-property/index.html.md).
-For example, consider you have a Blog Module with posts and authors. You can accept the user's language as a context and return the posts in the user's language. Another example is how Medusa uses Query Context to [retrieve product variants' prices based on the customer's currency](https://docs.medusajs.com/resources/commerce-modules/product/guides/price/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.
+
+
***
-## How to Use Query Context
+## One-to-Many Relationship
-The `query.graph` method accepts an optional `context` parameter that can be used to pass additional context either to the data model you're retrieving (for example, `post`), or its related and linked models (for example, `author`).
+A one-to-many relationship indicates that one record of a data model has many records of another data model.
-You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
+To define a one-to-many relationship, create relationship properties in the data models using the following methods:
-For example, to retrieve posts using Query while passing the user's language as a context:
+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.
-```ts
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
+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, you pass in the context a `lang` property whose value is `es`.
+In this example, a store has many products, but a product belongs to one store.
-Then, to handle the context while retrieving records of the data model, in the associated module's service you override the generated `list` method of the data model.
+### Optional Relationship
-For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
+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/configure-properties#nullable-property/index.html.md).
-```ts highlights={highlights2}
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
+### One-to-Many Relationship in the Database
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
+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:
- let posts = await super.listPosts(filters, config, sharedContext)
+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.
- if (context.lang === "es") {
- posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
+
- return posts
- }
-}
+***
-export default BlogModuleService
+## 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",
+ }),
+})
```
-In the above example, you override the generated `listPosts` method. This method receives as a first parameter the filters passed to the query, but it also includes a `context` property that holds the context passed to the query.
+The `manyToMany` method accepts two parameters:
-You extract the context from `filters`, then retrieve the posts using the parent's `listPosts` method. After that, if the language is set in the context, you transform the titles of the posts.
+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.
-All posts returned will now have their titles appended with "en español".
+The `pivotTable`, `joinColumn`, and `inverseJoinColumn` properties are only available after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7).
-Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
+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.
-### Using Pagination with Query
+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.
-If you pass pagination fields to `query.graph`, you must also override the `listAndCount` method in the service.
+### Many-to-Many Relationship in the Database
-For example, following along with the previous example, you must override the `listAndCountPosts` method of the Blog Module's service:
+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`.
-```ts
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
+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.
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listAndCountPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
+The pivot table has columns with foreign keys pointing to the primary key of the associated tables. The column's name is either:
- const result = await super.listAndCountPosts(
- filters,
- config,
- sharedContext
- )
+- 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`.
- if (context.lang === "es") {
- result.posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
+
- return result
- }
-}
+### Many-To-Many with Custom Columns
-export default BlogModuleService
+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(),
+})
```
-Now, the `listAndCountPosts` method will handle the context passed to `query.graph` when you pass pagination fields. You can also move the logic to transform the posts' titles to a separate method and call it from both `listPosts` and `listAndCountPosts`.
+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.
***
-## Passing Query Context to Related Data Models
+## Set Relationship Name in the Other Model
-If you're retrieving a data model and you want to pass context to its associated model in the same module, you can pass them as part of `QueryContext`'s parameter, then handle them in the same `list` method.
+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.
-For linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
+This is useful if the relationship property’s name is different from that of the associated data model.
-For example, to pass a context for the post's authors:
+As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
-```ts highlights={highlights3}
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
- author: QueryContext({
- lang: "es",
- }),
+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",
}),
})
```
-Then, in the `listPosts` method, you can handle the context for the post's authors:
+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`.
-```ts highlights={highlights4}
-import { MedusaContext, MedusaService } from "@medusajs/framework/utils"
-import { Context, FindConfig } from "@medusajs/framework/types"
-import Post from "./models/post"
-import Author from "./models/author"
+***
-class BlogModuleService extends MedusaService({
- Post,
- Author,
-}){
- // @ts-ignore
- async listPosts(
- filters?: any,
- config?: FindConfig | undefined,
- @MedusaContext() sharedContext?: Context | undefined
- ) {
- const context = filters.context ?? {}
- delete filters.context
+## Cascades
- let posts = await super.listPosts(filters, config, sharedContext)
+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.
- const isPostLangEs = context.lang === "es"
- const isAuthorLangEs = context.author?.lang === "es"
+For example, if a store is deleted, its products should also be deleted.
- if (isPostLangEs || isAuthorLangEs) {
- posts = posts.map((post) => {
- return {
- ...post,
- title: isPostLangEs ? post.title + " en español" : post.title,
- author: {
- ...post.author,
- name: isAuthorLangEs ? post.author.name + " en español" : post.author.name,
- },
- }
- })
- }
+The `cascades` method used on a data model configures which child records an operation is cascaded to.
- return posts
+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.
+
+
+# Write Migration
+
+In this chapter, you'll learn how to create a migration and write it manually.
+
+## What is a Migration?
+
+A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
+
+- The `up` method reflects changes on the database.
+- The `down` method reverts the changes made in the `up` method.
+
+***
+
+## How to Write a Migration?
+
+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 the specified modules' data models.
+
+Alternatively, you can manually create a migration file under the `migrations` directory of your module.
+
+For example:
+
+```ts title="src/modules/hello/migrations/Migration20240429.ts"
+import { Migration } from "@mikro-orm/migrations"
+
+export class Migration20240702105919 extends Migration {
+
+ async up(): Promise {
+ this.addSql("create table if not exists \"my_custom\" (\"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 \"my_custom_pkey\" primary key (\"id\"));")
}
-}
-export default BlogModuleService
+ async down(): Promise {
+ this.addSql("drop table if exists \"my_custom\" cascade;")
+ }
+
+}
```
-The context in `filters` will also have the context for `author`, which you can use to make transformations to the post's authors.
+The migration's file name should be of the format `Migration{YEAR}{MONTH}{DAY}.ts`. 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 `my_custom`, 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.
***
-## Passing Query Context to Linked Data Models
+## Run the Migration
-If you're retrieving a data model and you want to pass context to a linked model in a different module, pass to the `context` property an object instead, where its keys are the linked model's name and the values are the context for that linked model.
+To run your migration, run the following command:
-For example, consider the Product Module's `Product` data model is linked to the Blog Module's `Post` data model. You can pass context to the `Post` data model while retrieving products like so:
+This command also syncs module links. If you don't want that, use the `--skip-links` option.
-```ts highlights={highlights5}
-const { data } = await query.graph({
- entity: "product",
- fields: ["*", "post.*"],
- context: {
- post: QueryContext({
- lang: "es",
- }),
- },
-})
+```bash
+npx medusa db:migrate
```
-In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
+This reflects the changes in the database as implemented in the migration's `up` method.
-To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
+***
+
+## Rollback the Migration
+
+To rollback or revert the last migration you ran for a module, run the following command:
+
+```bash
+npx medusa db:rollback helloModuleService
+```
+
+This rolls back the last ran migration on the Hello Module.
+
+***
+
+## 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).
# Create a Plugin
@@ -10048,3657 +10189,3538 @@ npm publish
This will publish an updated version of your plugin under a new version.
-# Commerce Modules
+# Access Workflow Errors
-In this chapter, you'll learn about Medusa's commerce modules.
+In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
-## What is a Commerce Module?
+## How to Access Workflow Errors?
-Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more.
+By default, when an error occurs in a workflow, it throws that error, and the execution stops.
-Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart.
+You can configure the workflow to return the errors instead so that you can access and handle them differently.
-You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md)
+For example:
-The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically.
+```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"
-### List of Medusa's Commerce Modules
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result, errors } = await myWorkflow(req.scope)
+ .run({
+ // ...
+ throwOnError: false,
+ })
-Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa.
+ if (errors.length) {
+ return res.send({
+ errors: errors.map((error) => error.error),
+ })
+ }
-***
+ res.send(result)
+}
-## Use Commerce Modules in Custom Flows
+```
-Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features.
+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.
-For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods:
+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.
-```ts highlights={highlights}
-import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-export const countProductsStep = createStep(
- "count-products",
- async ({ }, { container }) => {
- const productModuleService = container.resolve("product")
+# Scheduled Jobs Number of Executions
- const [,count] = await productModuleService.listAndCountProducts()
+In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed.
- return new StepResponse(count)
- }
-)
-```
+## numberOfExecutions Option
-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.
+The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime.
+For example:
-# Architectural Modules
+```ts highlights={highlights}
+export default async function myCustomJob() {
+ console.log("I'll be executed three times only.")
+}
-In this chapter, you’ll learn about architectural modules.
+export const config = {
+ name: "hello-world",
+ // execute every minute
+ schedule: "* * * * *",
+ numberOfExecutions: 3,
+}
+```
-## What is an Architectural Module?
+The above scheduled job has the `numberOfExecutions` configuration set to `3`.
-An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
+So, it'll only execute 3 times, each every minute, then it won't be executed anymore.
-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.
+If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
-***
-## Architectural Module Types
+# Expose a Workflow Hook
-There are different architectural module types including:
+In this chapter, you'll learn how to expose a hook in your workflow.
-
+## When to Expose a Hook
-- 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.
+Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
-***
+Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
-## 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.
+## How to Expose a Hook in a Workflow?
+To expose a hook in your workflow, use `createHook` from the Workflows SDK.
-# Module Container
+For example:
-In this chapter, you'll learn about the module's container and how to resolve resources in that container.
+```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"
-Since modules are isolated, each module has a local container only used by the resources of that module.
+export const myWorkflow = createWorkflow(
+ "my-workflow",
+ function (input) {
+ const product = createProductStep(input)
+ const productCreatedHook = createHook(
+ "productCreated",
+ { productId: product.id }
+ )
-So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
+ return new WorkflowResponse(product, {
+ hooks: [productCreatedHook],
+ })
+ }
+)
+```
-### List of Registered Resources
+The `createHook` function accepts two parameters:
-Find a list of resources or dependencies registered in a module's container in [this Development Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md).
+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.
-## Resolve Resources
+### How to Consume the Hook?
-### Services
+To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
-A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
+```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
+import { myWorkflow } from "../my-workflow"
-For example:
+myWorkflow.hooks.productCreated(
+ async ({ productId }, { container }) => {
+ // TODO perform an action
+ }
+)
+```
-```ts highlights={[["4"], ["10"]]}
-import { Logger } from "@medusajs/framework/types"
+The hook is available on the workflow's `hooks` property using its name `productCreated`.
-type InjectedDependencies = {
- logger: Logger
-}
+You invoke the hook, passing a step function (the hook handler) as a parameter.
-export default class HelloModuleService {
- protected logger_: Logger
- constructor({ logger }: InjectedDependencies) {
- this.logger_ = logger
+# Workflow Constraints
- this.logger_.info("[HelloModuleService]: Hello World!")
- }
+This chapter lists constraints of defining a workflow or its steps.
- // ...
-}
-```
+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.
-### Loader
+This creates restrictions related to variable manipulations, using if-conditions, and other constraints. This chapter lists these constraints and provides their alternatives.
-A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
+## Workflow Constraints
-For example:
+### No Async Functions
-```ts highlights={[["9"]]}
-import {
- LoaderOptions,
-} from "@medusajs/framework/types"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
+The function passed to `createWorkflow` can’t be an async function:
-export default async function helloWorldLoader({
- container,
-}: LoaderOptions) {
- const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
+```ts highlights={[["4", "async", "Function can't be async."], ["11", "", "Correct way of defining the function."]]}
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ async function (input: WorkflowInput) {
+ // ...
+})
- logger.info("[helloWorldLoader]: Hello, World!")
-}
+// Do
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ // ...
+})
```
+### No Direct Variable Manipulation
-# Perform Database Operations in a Service
-
-In this chapter, you'll learn how to perform database operations in a module's service.
-
-This chapter is intended for more advanced database use-cases where you need more control over queries and operations. For basic database operations, such as creating or retrieving data of a model, use the [Service Factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) instead.
-
-## Run Queries
-
-[MikroORM's entity manager](https://mikro-orm.io/docs/entity-manager) is a class that has methods to run queries on the database and perform operations.
+You can’t directly manipulate variables within the workflow's constructor function.
-Medusa provides an `InjectManager` decorator from the Modules SDK that injects a service's method with a [forked entity manager](https://mikro-orm.io/docs/identity-map#forking-entity-manager).
+Learn more about why you can't manipulate variables [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md)
-So, to run database queries in a service:
+Instead, use `transform` from the Workflows SDK:
-1. Add the `InjectManager` decorator to the method.
-2. Add as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator from the Modules SDK. This context holds database-related context, including the manager injected by `InjectManager`
+```ts highlights={highlights}
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const str1 = step1(input)
+ const str2 = step2(input)
-For example, in your service, add the following methods:
+ return new WorkflowResponse({
+ message: `${str1}${str2}`,
+ })
+})
-```ts highlights={methodsHighlight}
-// other imports...
-import {
- InjectManager,
- MedusaContext,
-} from "@medusajs/framework/utils"
-import { SqlEntityManager } from "@mikro-orm/knex"
+// Do
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const str1 = step1(input)
+ const str2 = step2(input)
-class HelloModuleService {
- // ...
+ const result = transform(
+ {
+ str1,
+ str2,
+ },
+ (input) => ({
+ message: `${input.str1}${input.str2}`,
+ })
+ )
- @InjectManager()
- async getCount(
- @MedusaContext() sharedContext?: Context
- ): Promise {
- return await sharedContext.manager.count("my_custom")
- }
-
- @InjectManager()
- async getCountSql(
- @MedusaContext() sharedContext?: Context
- ): Promise {
- const data = await sharedContext.manager.execute(
- "SELECT COUNT(*) as num FROM my_custom"
- )
-
- return parseInt(data[0].num)
- }
-}
+ return new WorkflowResponse(result)
+})
```
-You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator.
+### Create Dates in transform
-The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager).
+When you use `new Date()` in a workflow's constructor function, the date is evaluated when Medusa creates the internal representation of the workflow, not during execution.
-You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count.
+Instead, create the date using `transform`.
-Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
+Learn more about how Medusa creates an internal representation of a workflow [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
-***
+For example:
-## Execute Operations in Transactions
+```ts highlights={dateHighlights}
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const today = new Date()
-To wrap database operations in a transaction, you create two methods:
+ return new WorkflowResponse({
+ today,
+ })
+})
-1. A private or protected method that's wrapped in a transaction. To wrap it in a transaction, you use the `InjectTransactionManager` decorator from the Modules SDK.
-2. A public method that calls the transactional method. You use on it the `InjectManager` decorator as explained in the previous section.
+// Do
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const today = transform({}, () => new Date())
-Both methods must accept as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator from the Modules SDK. It holds database-related contexts passed through the Medusa application.
+ return new WorkflowResponse({
+ today,
+ })
+})
+```
-For example:
+### No If Conditions
-```ts highlights={opHighlights}
-import {
- InjectManager,
- InjectTransactionManager,
- MedusaContext,
-} from "@medusajs/framework/utils"
-import { Context } from "@medusajs/framework/types"
-import { EntityManager } from "@mikro-orm/knex"
+You can't use if-conditions in a workflow.
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- protected async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- const transactionManager = sharedContext.transactionManager
- await transactionManager.nativeUpdate(
- "my_custom",
- {
- id: input.id,
- },
- {
- name: input.name,
- }
- )
+Learn more about why you can't use if-conditions [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions#why-if-conditions-arent-allowed-in-workflows/index.html.md)
- // retrieve again
- const updatedRecord = await transactionManager.execute(
- `SELECT * FROM my_custom WHERE id = '${input.id}'`
- )
+Instead, use when-then from the Workflows SDK:
- return updatedRecord
+```ts
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ if (input.is_active) {
+ // perform an action
}
+})
- @InjectManager()
- async update(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ) {
- return await this.update_(input, sharedContext)
- }
-}
+// Do (explained in the next chapter)
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ when(input, (input) => {
+ return input.is_active
+ })
+ .then(() => {
+ // perform an action
+ })
+})
```
-The `HelloModuleService` has two methods:
+You can also pair multiple `when-then` blocks to implement an `if-else` condition as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
-- A protected `update_` that performs the database operations inside a transaction.
-- A public `update` that executes the transactional protected method.
+### No Conditional Operators
-The shared context's `transactionManager` property holds the transactional entity manager (injected by `InjectTransactionManager`) that you use to perform database operations.
+You can't use conditional operators in a workflow, such as `??` or `||`.
-Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
+Learn more about why you can't use conditional operators [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions#why-if-conditions-arent-allowed-in-workflows/index.html.md)
-### Why Wrap a Transactional Method
+Instead, use `transform` to store the desired value in a variable.
-The variables in the transactional method (for example, `update_`) hold values that are uncommitted to the database. They're only committed once the method finishes execution.
+### Logical Or (||) Alternative
-So, if in your method you perform database operations, then use their result to perform other actions, such as connecting to a third-party service, you'll be working with uncommitted data.
+```ts
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const message = input.message || "Hello"
+})
-By placing only the database operations in a method that has the `InjectTransactionManager` and using it in a wrapper method, the wrapper method receives the committed result of the transactional method.
+// Do
+// other imports...
+import { transform } from "@medusajs/framework/workflows-sdk"
-This is also useful if you perform heavy data normalization outside of the database operations. In that case, you don't hold the transaction for a longer time than needed.
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const message = transform(
+ {
+ input,
+ },
+ (data) => data.input.message || "hello"
+ )
+})
+```
-For example, the `update` method could be changed to the following:
+### Nullish Coalescing (??) Alternative
```ts
-// other imports...
-import { EntityManager } from "@mikro-orm/knex"
-
-class HelloModuleService {
- // ...
- @InjectManager()
- async update(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ) {
- const newData = await this.update_(input, sharedContext)
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const message = input.message ?? "Hello"
+})
- await sendNewDataToSystem(newData)
+// Do
+// other imports...
+import { transform } from "@medusajs/framework/workflows-sdk"
- return newData
- }
-}
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const message = transform(
+ {
+ input,
+ },
+ (data) => data.input.message ?? "hello"
+ )
+})
```
-In this case, only the `update_` method is wrapped in a transaction. The returned value `newData` holds the committed result, which can be used for other operations, such as passed to a `sendNewDataToSystem` method.
+### Double Not (!!) Alternative
-### Using Methods in Transactional Methods
+```ts
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ step1({
+ isActive: !!input.is_active,
+ })
+})
-If your transactional method uses other methods that accept a Medusa context, pass the shared context to those methods.
+// Do
+// other imports...
+import { transform } from "@medusajs/framework/workflows-sdk"
-For example:
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const isActive = transform(
+ {
+ input,
+ },
+ (data) => !!data.input.is_active
+ )
+
+ step1({
+ isActive,
+ })
+})
+```
+
+### Ternary Alternative
```ts
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ step1({
+ message: input.is_active ? "active" : "inactive",
+ })
+})
+
+// Do
// other imports...
-import { EntityManager } from "@mikro-orm/knex"
+import { transform } from "@medusajs/framework/workflows-sdk"
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- protected async anotherMethod(
- @MedusaContext() sharedContext?: Context
- ) {
- // ...
- }
-
- @InjectTransactionManager()
- protected async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- this.anotherMethod(sharedContext)
- }
-}
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const message = transform(
+ {
+ input,
+ },
+ (data) => {
+ return data.input.is_active ? "active" : "inactive"
+ }
+ )
+
+ step1({
+ message,
+ })
+})
```
-You use the `anotherMethod` transactional method in the `update_` transactional method, so you pass it the shared context.
+### Optional Chaining (?.) Alternative
-The `anotherMethod` now runs in the same transaction as the `update_` method.
+```ts
+// Don't
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ step1({
+ name: input.customer?.name,
+ })
+})
-***
+// Do
+// other imports...
+import { transform } from "@medusajs/framework/workflows-sdk"
-## Configure Transactions
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input: WorkflowInput) {
+ const name = transform(
+ {
+ input,
+ },
+ (data) => data.input.customer?.name
+ )
+
+ step1({
+ name,
+ })
+})
+```
-To configure the transaction, such as its [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html), use the `baseRepository` dependency registered in your module's container.
+***
-The `baseRepository` is an instance of a repository class that provides methods to create transactions, run database operations, and more.
+## Step Constraints
-The `baseRepository` has a `transaction` method that allows you to run a function within a transaction and configure that transaction.
+### Returned Values
-For example, resolve the `baseRepository` in your service's constructor:
+A step must only return serializable values, such as [primitive values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) or an object.
-### Extending Service Factory
+Values of other types, such as Maps, aren't allowed.
-```ts highlights={[["14"]]}
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
-import { DAL } from "@medusajs/framework/types"
+```ts
+// Don't
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
-type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
-}
+const step1 = createStep(
+ "step-1",
+ (input, { container }) => {
+ const myMap = new Map()
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- protected baseRepository_: DAL.RepositoryService
+ // ...
- constructor({ baseRepository }: InjectedDependencies) {
- super(...arguments)
- this.baseRepository_ = baseRepository
+ return new StepResponse({
+ myMap,
+ })
}
-}
-
-export default HelloModuleService
-```
-
-### Without Service Factory
+)
-```ts highlights={[["10"]]}
-import { DAL } from "@medusajs/framework/types"
+// Do
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
-type InjectedDependencies = {
- baseRepository: DAL.RepositoryService
-}
+const step1 = createStep(
+ "step-1",
+ (input, { container }) => {
+ const myObj: Record = {}
-class HelloModuleService {
- protected baseRepository_: DAL.RepositoryService
+ // ...
- constructor({ baseRepository }: InjectedDependencies) {
- this.baseRepository_ = baseRepository
+ return new StepResponse({
+ myObj,
+ })
}
-}
-
-export default HelloModuleService
+)
```
-Then, add the following method that uses it:
-
-```ts highlights={repoHighlights}
-// ...
-import {
- InjectManager,
- InjectTransactionManager,
- MedusaContext,
-} from "@medusajs/framework/utils"
-import { Context } from "@medusajs/framework/types"
-import { EntityManager } from "@mikro-orm/knex"
-
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- protected async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- return await this.baseRepository_.transaction(
- async (transactionManager) => {
- await transactionManager.nativeUpdate(
- "my_custom",
- {
- id: input.id,
- },
- {
- name: input.name,
- }
- )
-
- // retrieve again
- const updatedRecord = await transactionManager.execute(
- `SELECT * FROM my_custom WHERE id = '${input.id}'`
- )
- return updatedRecord
- },
- {
- transaction: sharedContext.transactionManager,
- }
- )
- }
+# Conditions in Workflows with When-Then
- @InjectManager()
- async update(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ) {
- return await this.update_(input, sharedContext)
- }
-}
-```
+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.
-The `update_` method uses the `baseRepository_.transaction` method to wrap a function in a transaction.
+## Why If-Conditions Aren't Allowed in Workflows?
-The function parameter receives a transactional entity manager as a parameter. Use it to perform the database operations.
+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.
-The `baseRepository_.transaction` method also receives as a second parameter an object of options. You must pass in it the `transaction` property and set its value to the `sharedContext.transactionManager` property so that the function wrapped in the transaction uses the injected transaction manager.
+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.
-Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
+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.
-### Transaction Options
+Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
-The second parameter of the `baseRepository_.transaction` method is an object of options that accepts the following properties:
+***
-1. `transaction`: Set the transactional entity manager passed to the function. You must provide this option as explained in the previous section.
+## How to use When-Then?
-```ts highlights={[["16"]]}
-// other imports...
-import { EntityManager } from "@mikro-orm/knex"
+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.
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- return await this.baseRepository_.transaction(
- async (transactionManager) => {
- // ...
- },
- {
- transaction: sharedContext.transactionManager,
- }
- )
- }
-}
-```
+For example:
-2. `isolationLevel`: Sets the transaction's [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html). Its values can be:
- - `read committed`
- - `read uncommitted`
- - `snapshot`
- - `repeatable read`
- - `serializable`
+```ts highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ when,
+} from "@medusajs/framework/workflows-sdk"
+// step imports...
-```ts highlights={[["19"]]}
-// other imports...
-import { IsolationLevel } from "@mikro-orm/core"
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- return await this.baseRepository_.transaction(
- async (transactionManager) => {
- // ...
- },
- {
- isolationLevel: IsolationLevel.READ_COMMITTED,
+ const result = when(
+ input,
+ (input) => {
+ return input.is_active
}
- )
- }
-}
-```
+ ).then(() => {
+ const stepResult = isActiveStep()
+ return stepResult
+ })
-3. `enableNestedTransactions`: (default: `false`) whether to allow using nested transactions.
- - If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
+ // executed without condition
+ const anotherStepResult = anotherStep(result)
-```ts highlights={[["16"]]}
-class HelloModuleService {
- // ...
- @InjectTransactionManager()
- async update_(
- input: {
- id: string,
- name: string
- },
- @MedusaContext() sharedContext?: Context
- ): Promise {
- return await this.baseRepository_.transaction(
- async (transactionManager) => {
- // ...
- },
- {
- enableNestedTransactions: false,
- }
+ return new WorkflowResponse(
+ anotherStepResult
)
}
-}
+)
```
+In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
-# 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
+### When Parameters
-Some of the module isolation's benefits include:
+`when` accepts the following parameters:
-- 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.
+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
-## How to Extend Data Model of Another Module?
+To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
-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).
+The callback function is only executed if `when`'s second parameter function returns a `true` value.
***
-## 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
+## Implementing If-Else with When-Then
-For example, consider you have two modules:
+when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
-1. A module that stores and manages brands in your application.
-2. A module that integrates a third-party Content Management System (CMS).
+For example:
-To sync brands from your application to the third-party system, create the following steps:
+```ts highlights={ifElseHighlights}
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
-```ts title="Example Steps" highlights={stepsHighlights}
-const retrieveBrandsStep = createStep(
- "retrieve-brands",
- async (_, { container }) => {
- const brandModuleService = container.resolve(
- "brandModuleService"
- )
+ const isActiveResult = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+ ).then(() => {
+ return isActiveStep()
+ })
- const brands = await brandModuleService.listBrands()
+ const notIsActiveResult = when(
+ input,
+ (input) => {
+ return !input.is_active
+ }
+ ).then(() => {
+ return notIsActiveStep()
+ })
- return new StepResponse(brands)
+ // ...
}
)
+```
-const createBrandsInCmsStep = createStep(
- "create-brands-in-cms",
- async ({ brands }, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
+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.
- const cmsBrands = await cmsModuleService.createBrands(brands)
+***
- return new StepResponse(cmsBrands, cmsBrands)
- },
- async (brands, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
+## Specify Name for When-Then
- await cmsModuleService.deleteBrands(
- brands.map((brand) => brand.id)
- )
+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()
+})
```
-The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
+This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
-Then, create the following workflow that uses these steps:
+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.
-```ts title="Example Workflow"
-export const syncBrandsWorkflow = createWorkflow(
- "sync-brands",
- () => {
- const brands = retrieveBrandsStep()
+You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
- createBrandsInCmsStep({ brands })
+```ts highlights={nameHighlights}
+const { isActive } = when(
+ "check-is-active",
+ input,
+ (input) => {
+ return input.is_active
}
-)
-```
+).then(() => {
+ const isActive = isActiveStep()
-You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
+ return {
+ isActive,
+ }
+})
+```
+Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
-# Loaders
+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`.
-In this chapter, you’ll learn about loaders and how to use them.
+The second and third parameters are the same as the parameters you previously passed to `when`.
-## What is a Loader?
-When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup.
+# Compensation Function
-In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules.
+In this chapter, you'll learn what a compensation function is and how to add it to a step.
-Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module.
+## What is a Compensation Function
-Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md).
+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.
-## How to Create a Loader?
+By using compensation functions, you provide a mechanism that guarantees data consistency in your application and across systems.
-### 1. Implement Loader Function
+***
-You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory.
+## How to add a Compensation Function?
-For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content:
+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:
-Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+```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"
-```ts title="src/modules/hello/loaders/hello-world.ts"
-import {
- LoaderOptions,
-} from "@medusajs/framework/types"
+const step1 = createStep(
+ "step-1",
+ async () => {
+ const message = `Hello from step one!`
-export default async function helloWorldLoader({
- container,
-}: LoaderOptions) {
- const logger = container.resolve("logger")
+ console.log(message)
- logger.info("[helloWorldLoader]: Hello, World!")
-}
+ return new StepResponse(message)
+ },
+ async () => {
+ console.log("Oops! Rolling back my changes...")
+ }
+)
```
-The loader file exports an async function, which is the function executed when the application loads.
+Each step can have a compensation function. The compensation function only runs if an error occurs throughout the workflow.
-The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal.
+***
-Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md).
+## Test the Compensation Function
-### 2. Export Loader in Module Definition
+Create a step in the same `src/workflows/hello-world.ts` file that throws an error:
-After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it.
+```ts title="src/workflows/hello-world.ts"
+const step2 = createStep(
+ "step-2",
+ async () => {
+ throw new Error("Throwing an error...")
+ }
+)
+```
-So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`:
+Then, create a workflow that uses the steps:
-```ts title="src/modules/hello/index.ts"
+```ts title="src/workflows/hello-world.ts" collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
// other imports...
-import helloWorldLoader from "./loaders/hello-world"
-export default Module("hello", {
- // ...
- loaders: [helloWorldLoader],
+// steps...
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ const str1 = step1()
+ step2()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
})
+
+export default myWorkflow
```
-The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts.
+Finally, execute the workflow from an API route:
-### Test the Loader
+```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"
-Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application:
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await myWorkflow(req.scope)
+ .run()
-```bash npm2yarn
-npm run dev
+ res.send(result)
+}
```
-Then, you'll find the following message logged in the terminal:
+Run the Medusa application and send a `GET` request to `/workflow`:
-```plain
-info: [HELLO MODULE] Just started the Medusa application!
+```bash
+curl http://localhost:9000/workflow
```
-This indicates that the loader in the `hello` module ran and logged this message.
+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.
***
-## Example: Register Custom MongoDB Connection
+## Pass Input to Compensation Function
-As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module.
+If a step creates a record, the compensation function must receive the ID of the record to remove it.
-Consider your have a MongoDB module that allows you to perform operations on a MongoDB database.
+To pass input to the compensation function, pass a second parameter in the `StepResponse` returned by the step.
-### Prerequisites
+For example:
-- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com)
-- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver)
+```ts highlights={inputHighlights}
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
-To connect to the database, you create the following loader in your module:
+const step1 = createStep(
+ "step-1",
+ async () => {
+ return new StepResponse(
+ `Hello from step one!`,
+ { message: "Oops! Rolling back my changes..." }
+ )
+ },
+ async ({ message }) => {
+ console.log(message)
+ }
+)
+```
-```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
-import { LoaderOptions } from "@medusajs/framework/types"
-import { asValue } from "awilix"
-import { MongoClient } from "mongodb"
+In this example, the step passes an object as a second parameter to `StepResponse`.
-type ModuleOptions = {
- connection_url?: string
- db_name?: string
-}
+The compensation function receives the object and uses its `message` property to log a message.
-export default async function mongoConnectionLoader({
- container,
- options,
-}: LoaderOptions) {
- if (!options.connection_url) {
- throw new Error(`[MONGO MDOULE]: connection_url option is required.`)
- }
- if (!options.db_name) {
- throw new Error(`[MONGO MDOULE]: db_name option is required.`)
- }
- const logger = container.resolve("logger")
-
- try {
- const clientDb = (
- await (new MongoClient(options.connection_url)).connect()
- ).db(options.db_name)
+***
- logger.info("Connected to MongoDB")
+## Resolve Resources from the Medusa Container
- container.register(
- "mongoClient",
- asValue(clientDb)
+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..." }
)
- } catch (e) {
- logger.error(
- `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}`
+ },
+ async ({ message }, { container }) => {
+ const logger = container.resolve(
+ ContainerRegistrationKeys.LOGGER
)
+
+ logger.info(message)
}
-}
+)
```
-The loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example:
-
-```ts title="medusa-config.ts" highlights={optionHighlights}
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/mongo",
- options: {
- connection_url: process.env.MONGO_CONNECTION_URL,
- db_name: process.env.MONGO_DB_NAME,
- },
- },
- ],
-})
-```
+In this example, you use the `container` property in the second object parameter of the compensation function to resolve the logger.
-Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options:
+You then use the logger to log a message.
-- `connection_url`: the URL to connect to the MongoDB database.
-- `db_name`: The name of the database to connect to.
+***
-In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options.
+## Handle Errors in Loops
-After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters:
+This feature is only available after [Medusa v2.0.5](https://github.com/medusajs/medusa/releases/tag/v2.0.5).
-1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client.
-2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
+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:
-### Use Custom Registered Resource in Module's Service
+```ts
+// other imports...
+import { promiseAll } from "@medusajs/framework/utils"
-After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service.
+type StepInput = {
+ ids: string[]
+}
-For example:
+const step1 = createStep(
+ "step-1",
+ async ({ ids }: StepInput, { container }) => {
+ const erpModuleService = container.resolve(
+ ERP_MODULE
+ )
+ const prevData: unknown[] = []
-```ts title="src/modules/mongo/service.ts"
-import type { Db } from "mongodb"
+ await promiseAll(
+ ids.map(async (id) => {
+ const data = await erpModuleService.retrieve(id)
-type InjectedDependencies = {
- mongoClient: Db
-}
+ await erpModuleService.delete(id)
-export default class MongoModuleService {
- private mongoClient_: Db
+ prevData.push(id)
+ })
+ )
- constructor({ mongoClient }: InjectedDependencies) {
- this.mongoClient_ = mongoClient
+ return new StepResponse(ids, prevData)
}
+)
+```
- async createMovie({ title }: {
- title: string
- }) {
- const moviesCol = this.mongoClient_.collection("movie")
+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.
- const insertedMovie = await moviesCol.insertOne({
- title,
- })
+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.
- const movie = await moviesCol.findOne({
- _id: insertedMovie.insertedId,
- })
+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:
- return movie
- }
+```ts highlights={highlights}
+try {
+ await promiseAll(
+ ids.map(async (id) => {
+ const data = await erpModuleService.retrieve(id)
- async deleteMovie(id: string) {
- const moviesCol = this.mongoClient_.collection("movie")
+ await erpModuleService.delete(id)
- await moviesCol.deleteOne({
- _id: {
- equals: id,
- },
+ prevData.push(id)
})
- }
+ )
+} catch (e) {
+ return StepResponse.permanentFailure(
+ `An error occurred: ${e}`,
+ prevData
+ )
}
```
-The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively.
-
-Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module:
-
-```ts title="src/modules/mongo/index.ts" highlights={[["9"]]}
-import { Module } from "@medusajs/framework/utils"
-import MongoModuleService from "./service"
-import mongoConnectionLoader from "./loaders/connection"
+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.
-export const MONGO_MODULE = "mongo"
+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.
-export default Module(MONGO_MODULE, {
- service: MongoModuleService,
- loaders: [mongoConnectionLoader],
-})
-```
-### Test it Out
+# Execute Another Workflow
-You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal:
+In this chapter, you'll learn how to execute a workflow in another.
-```bash
-info: Connected to MongoDB
-```
+## Execute in a Workflow
-You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.
+To execute a workflow in another, use the `runAsStep` method that every workflow has.
+For example:
-# Modules Directory Structure
+```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports"
+import {
+ createWorkflow,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
-In this document, you'll learn about the expected files and directories in your module.
+const workflow = createWorkflow(
+ "hello-world",
+ async (input) => {
+ const products = createProductsWorkflow.runAsStep({
+ input: {
+ products: [
+ // ...
+ ],
+ },
+ })
-
+ // ...
+ }
+)
+```
-## index.ts
+Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter.
-The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+The object has an `input` property to pass input to the workflow.
***
-## service.ts
+## Preparing Input Data
-A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK.
-***
+Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
-## Other Directories
+For example:
-The following directories are optional and their content are explained more in the following chapters:
+```ts highlights={transformHighlights} collapsibleLines="1-12"
+import {
+ createWorkflow,
+ transform,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
-- `models`: Holds the data models representing tables in the database.
-- `migrations`: Holds the migration files used to reflect changes on the database.
-- `loaders`: Holds the scripts to run on the Medusa application's start-up.
+type WorkflowInput = {
+ title: string
+}
+const workflow = createWorkflow(
+ "hello-product",
+ async (input: WorkflowInput) => {
+ const createProductsData = transform({
+ input,
+ }, (data) => [
+ {
+ title: `Hello ${data.input.title}`,
+ },
+ ])
-# Service Constraints
+ const products = createProductsWorkflow.runAsStep({
+ input: {
+ products: createProductsData,
+ },
+ })
-This chapter lists constraints to keep in mind when creating a service.
+ // ...
+ }
+)
+```
-## Use Async Methods
+In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`.
-Medusa wraps service method executions to inject useful context or transactions. However, since Medusa can't detect whether the method is asynchronous, it always executes methods in the wrapper with the `await` keyword.
+***
-For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method:
+## Run Workflow Conditionally
-```ts
-await helloModuleService.getMessage()
-```
+To run a workflow in another based on a condition, use when-then from the Workflows SDK.
-So, make sure your service's methods are always async to avoid unexpected errors or behavior.
+Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
-```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]}
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
+For example:
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- // Don't
- getMessage(): string {
- return "Hello, World!"
- }
+```ts highlights={whenHighlights} collapsibleLines="1-16"
+import {
+ createWorkflow,
+ when,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+import {
+ CreateProductWorkflowInputDTO,
+} from "@medusajs/framework/types"
- // Do
- async getMessage(): Promise {
- return "Hello, World!"
- }
+type WorkflowInput = {
+ product?: CreateProductWorkflowInputDTO
+ should_create?: boolean
}
-export default HelloModuleService
+const workflow = createWorkflow(
+ "hello-product",
+ async (input: WorkflowInput) => {
+ const product = when(input, ({ should_create }) => should_create)
+ .then(() => {
+ return createProductsWorkflow.runAsStep({
+ input: {
+ products: [input.product],
+ },
+ })
+ })
+ }
+)
```
+In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
-# 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.
+# Run Workflow Steps in Parallel
-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.
+In this chapter, you’ll learn how to run workflow steps in parallel.
-***
+## parallelize Utility Function
-## How to Add an Internal Service
+If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK.
-### 1. Create Service
+The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
-To add an internal service, create it in the `services` directory of your module.
+For example:
-For example, create the file `src/modules/hello/services/client.ts` with the following content:
+```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"
-```ts title="src/modules/hello/services/client.ts"
-export class ClientService {
- async getMessage(): Promise {
- return "Hello, World!"
- }
+interface WorkflowInput {
+ title: string
}
-```
-### 2. Export Service in Index
+const myWorkflow = createWorkflow(
+ "my-workflow",
+ (input: WorkflowInput) => {
+ const product = createProductStep(input)
-Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
+ const [prices, productSalesChannel] = parallelize(
+ createPricesStep(product),
+ attachProductToSalesChannelStep(product)
+ )
-For example, create the file `src/modules/hello/services/index.ts` with the following content:
+ const id = product.id
+ const refetchedProduct = getProductStep(product.id)
-```ts title="src/modules/hello/services/index.ts"
-export * from "./client"
+ return new WorkflowResponse(refetchedProduct)
+ }
+)
```
-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:
+The `parallelize` function accepts the steps to run in parallel as a parameter.
-```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]}
-// other imports...
-import { ClientService } from "./services"
+It returns an array of the steps' results in the same order they're passed to the `parallelize` function.
-type InjectedDependencies = {
- clientService: ClientService
-}
+So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- protected clientService_: ClientService
- constructor({ clientService }: InjectedDependencies) {
- super(...arguments)
- this.clientService_ = clientService
- }
-}
-```
+# Multiple Step Usage in Workflow
-You can now use your internal service in your main service.
+In this chapter, you'll learn how to use a step multiple times in a workflow.
-***
+## Problem Reusing a Step in a Workflow
-## Resolve Resources in Internal Service
+In some cases, you may need to use a step multiple times in the same workflow.
-Resolve dependencies from your module's container in the constructor of your internal service.
+The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products.
-For example:
+Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step:
```ts
-import { Logger } from "@medusajs/framework/types"
+const useQueryGraphStep = createStep(
+ "use-query-graph"
+ // ...
+)
+```
-type InjectedDependencies = {
- logger: Logger
-}
+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:
-export class ClientService {
- protected logger_: Logger
+```ts
+const helloWorkflow = createWorkflow(
+ "hello",
+ () => {
+ const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id"],
+ })
- constructor({ logger }: InjectedDependencies) {
- this.logger_ = logger
+ // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW
+ const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: ["id"],
+ })
}
-}
+)
```
-***
-
-## Access Module Options
-
-Your internal service can't access the module's options.
+The next section explains how to fix this issue to use the same step multiple times in a workflow.
-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:
+## How to Use a Step Multiple Times in a Workflow?
-```ts
-import { ConfigModule } from "@medusajs/framework/types"
-import { HELLO_MODULE } from ".."
+When you execute a step in a workflow, you can chain a `config` method to it to change the step's config.
-export type InjectedDependencies = {
- configModule: ConfigModule
-}
+Use the `config` method to change a step's ID for a single execution.
-export class ClientService {
- protected options: Record
+So, this is the correct way to write the example above:
- constructor({ configModule }: InjectedDependencies) {
- const moduleDef = configModule.modules[HELLO_MODULE]
+```ts highlights={highlights}
+const helloWorkflow = createWorkflow(
+ "hello",
+ () => {
+ const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: ["id"],
+ })
- if (typeof moduleDef !== "boolean") {
- this.options = moduleDef.options
- }
+ // ✓ No error occurs, the step has a different ID.
+ const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: ["id"],
+ }).config({ name: "fetch-customers" })
}
-}
+)
```
-The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key.
+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.
-If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
+The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`.
-# Module Options
+# Long-Running Workflows
-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.
+In this chapter, you’ll learn what a long-running workflow is and how to configure it.
-## What are Module Options?
+## What is a Long-Running Workflow?
-A module can receive options to customize or configure its functionality. For example, if you’re creating a module that integrates a third-party service, you’ll want to receive the integration credentials in the options rather than adding them directly in your code.
+When you execute a workflow, you wait until the workflow finishes execution to receive the output.
-***
+A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished.
-## How to Pass Options to a Module?
+### Why use Long-Running Workflows?
-To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`.
+Long-running workflows are useful if:
-For example:
+- A task takes too long. For example, you're importing data from a CSV file.
+- The workflow's steps wait for an external action to finish before resuming execution. For example, before you import the data from the CSV file, you wait until the import is confirmed by the user.
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/hello",
- options: {
- capitalize: true,
- },
- },
- ],
-})
-```
+***
-The `options` property’s value is an object. You can pass any properties you want.
+## Configure Long-Running Workflows
-### Pass Options to a Module in a Plugin
+A workflow is considered long-running if at least one step has its `async` configuration set to `true` and doesn't return a step response.
-If your module is part of a plugin, you can pass options to the module in the plugin’s configuration.
+For example, consider the following workflow and steps:
-For example:
+```ts title="src/workflows/hello-world.ts" highlights={[["15"]]} collapsibleLines="1-11" expandButtonLabel="Show More"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
-```ts title="medusa-config.ts"
-import { defineConfig } from "@medusajs/framework/utils"
-module.exports = defineConfig({
- plugins: [
- {
- resolve: "@myorg/plugin-name",
- options: {
- capitalize: true,
- },
- },
- ],
+const step1 = createStep("step-1", async () => {
+ return new StepResponse({})
})
-```
-
-The `options` property in the plugin configuration is passed to all modules in a plugin.
-
-***
-
-## Access Module Options in Main Service
-The module’s main service receives the module options as a second parameter.
+const step2 = createStep(
+ {
+ name: "step-2",
+ async: true,
+ },
+ async () => {
+ console.log("Waiting to be successful...")
+ }
+)
-For example:
+const step3 = createStep("step-3", async () => {
+ return new StepResponse("Finished three steps")
+})
-```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function () {
+ step1()
+ step2()
+ const message = step3()
-// recommended to define type in another file
-type ModuleOptions = {
- capitalize?: boolean
-}
+ return new WorkflowResponse({
+ message,
+ })
+})
-export default class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- protected options_: ModuleOptions
+export default myWorkflow
+```
- constructor({}, options?: ModuleOptions) {
- super(...arguments)
+The second step has in its configuration object `async` set to `true` and it doesn't return a step response. This indicates that this step is an asynchronous step.
- this.options_ = options || {
- capitalize: false,
- }
- }
+So, when you execute the `hello-world` workflow, it continues its execution in the background once it reaches the second step.
- // ...
-}
-```
+A workflow is also considered long-running if one of its steps has their `retryInterval` option set as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/retry-failed-steps/index.html.md).
***
-## Access Module Options in Loader
+## Change Step Status
-The object that a module’s loaders receive as a parameter has an `options` property holding the module's options.
+Once the workflow's execution reaches an async step, it'll wait in the background for the step to succeed or fail before it moves to the next step.
-For example:
+To fail or succeed a step, use the Workflow Engine Module's main service that is registered in the Medusa Container under the `Modules.WORKFLOW_ENGINE` (or `workflowsModuleService`) key.
-```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]}
-import {
- LoaderOptions,
-} from "@medusajs/framework/types"
+### Retrieve Transaction ID
-// recommended to define type in another file
-type ModuleOptions = {
- capitalize?: boolean
-}
+Before changing the status of a workflow execution's async step, you must have the execution's transaction ID.
-export default async function helloWorldLoader({
- options,
-}: LoaderOptions) {
-
- console.log(
- "[HELLO MODULE] Just started the Medusa application!",
- options
- )
-}
-```
+When you execute the workflow, the object returned has a `transaction` property, which is an object that holds the details of the workflow execution's transaction. Use its `transactionId` to later change async steps' statuses:
-***
+```ts
+const { transaction } = await myWorkflow(req.scope)
+ .run()
-## Validate Module Options
+// use transaction.transactionId later
+```
-If you expect a certain option and want to throw an error if it's not provided or isn't valid, it's recommended to perform the validation in a loader. The module's service is only instantiated when it's used, whereas the loader runs the when the Medusa application starts.
+### Change Step Status to Successful
-So, by performing the validation in the loader, you ensure you can throw an error at an early point, rather than when the module is used.
+The Workflow Engine Module's main service has a `setStepSuccess` method to set a step's status to successful. If you use it on a workflow execution's async step, the workflow continues execution to the next step.
-For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`:
+For example, consider the following step:
-```ts title="src/modules/hello/loaders/validate.ts"
-import { LoaderOptions } from "@medusajs/framework/types"
-import { MedusaError } from "@medusajs/framework/utils"
+```ts highlights={successStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ Modules,
+ TransactionHandlerType,
+} from "@medusajs/framework/utils"
+import {
+ StepResponse,
+ createStep,
+} from "@medusajs/framework/workflows-sdk"
-// recommended to define type in another file
-type ModuleOptions = {
- apiKey?: string
-}
+type SetStepSuccessStepInput = {
+ transactionId: string
+};
-export default async function validationLoader({
- options,
-}: LoaderOptions) {
- if (!options.apiKey) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Hello Module requires an apiKey option."
+export const setStepSuccessStep = createStep(
+ "set-step-success-step",
+ async function (
+ { transactionId }: SetStepSuccessStepInput,
+ { container }
+ ) {
+ const workflowEngineService = container.resolve(
+ Modules.WORKFLOW_ENGINE
)
+
+ await workflowEngineService.setStepSuccess({
+ idempotencyKey: {
+ action: TransactionHandlerType.INVOKE,
+ transactionId,
+ stepId: "step-2",
+ workflowId: "hello-world",
+ },
+ stepResponse: new StepResponse("Done!"),
+ options: {
+ container,
+ },
+ })
}
-}
+)
```
-Then, export the loader in the module's definition file, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md):
-
-```ts title="src/modules/hello/index.ts"
-// other imports...
-import validationLoader from "./loaders/validate"
-
-export default Module("hello", {
- // ...
- loaders: [validationLoader],
-})
-```
+In this step (which you use in a workflow other than the long-running workflow), you resolve the Workflow Engine Module's main service and set `step-2` of the previous workflow as successful.
-Now, when the Medusa application starts, the loader will run, validating the module's options and throwing an error if the `apiKey` option is missing.
+The `setStepSuccess` method of the workflow engine's main service accepts as a parameter an object having the following properties:
+- idempotencyKey: (\`object\`) The details of the workflow execution.
-# Service Factory
+ - action: (\`invoke\` | \`compensate\`) If the step's compensation function is running, use \`compensate\`. Otherwise, use \`invoke\`.
-In this chapter, you’ll learn about what the service factory is and how to use it.
+ - transactionId: (\`string\`) The ID of the workflow execution's transaction.
-## What is the Service Factory?
+ - stepId: (\`string\`) The ID of the step to change its status. This is the first parameter passed to \`createStep\` when creating the step.
-Medusa provides a service factory that your module’s main service can extend.
+ - workflowId: (\`string\`) The ID of the workflow. This is the first parameter passed to \`createWorkflow\` when creating the workflow.
+- stepResponse: (\`StepResponse\`) Set the response of the step. This is similar to the response you return in a step's definition, but since the \`async\` step doesn't have a response, you set its response when changing its status.
+- options: (\`Record\\`) Options to pass to the step.
-The service factory generates data management methods for your data models in the database, so you don't have to implement these methods manually.
+ - container: (\`MedusaContainer\`) An instance of the Medusa Container
-Your service provides data-management functionalities of your data models.
+### Change Step Status to Failed
-***
+The Workflow Engine Module's main service also has a `setStepFailure` method that changes a step's status to failed. It accepts the same parameter as `setStepSuccess`.
-## How to Extend the Service Factory?
+After changing the async step's status to failed, the workflow execution fails and the compensation functions of previous steps are executed.
-Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods.
+For example:
-For example, create the file `src/modules/hello/service.ts` with the following content:
+```ts highlights={failureStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ Modules,
+ TransactionHandlerType,
+} from "@medusajs/framework/utils"
+import {
+ StepResponse,
+ createStep,
+} from "@medusajs/framework/workflows-sdk"
-```ts title="src/modules/hello/service.ts" highlights={highlights}
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
+type SetStepFailureStepInput = {
+ transactionId: string
+};
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- // TODO implement custom methods
-}
+export const setStepFailureStep = createStep(
+ "set-step-success-step",
+ async function (
+ { transactionId }: SetStepFailureStepInput,
+ { container }
+ ) {
+ const workflowEngineService = container.resolve(
+ Modules.WORKFLOW_ENGINE
+ )
-export default HelloModuleService
+ await workflowEngineService.setStepFailure({
+ idempotencyKey: {
+ action: TransactionHandlerType.INVOKE,
+ transactionId,
+ stepId: "step-2",
+ workflowId: "hello-world",
+ },
+ stepResponse: new StepResponse("Failed!"),
+ options: {
+ container,
+ },
+ })
+ }
+)
```
-### MedusaService Parameters
+You use this step in another workflow that changes the status of an async step in a long-running workflow's execution to failed.
-The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
+***
-In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`.
+## Access Long-Running Workflow Status and Result
-### Generated Methods
+To access the status and result of a long-running workflow execution, use the `subscribe` and `unsubscribe` methods of the Workflow Engine Module's main service.
-The service factory generates methods to manage the records of each of the data models provided in the first parameter in the database.
+To retrieve the workflow execution's details at a later point, you must enable [storing the workflow's executions](https://docs.medusajs.com/learn/fundamentals/workflows/store-executions/index.html.md).
-The method's names are the operation's name, suffixed by the data model's key in the object parameter passed to `MedusaService`.
+For example:
-For example, the following methods are generated for the service above:
+```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-11" expandButtonLabel="Show Imports"
+import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import myWorkflow from "../../../workflows/hello-world"
+import {
+ IWorkflowEngineService,
+} from "@medusajs/framework/types"
+import { Modules } from "@medusajs/framework/utils"
-Find a complete reference of each of the methods in [this documentation](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
+export async function GET(req: MedusaRequest, res: MedusaResponse) {
+ const { transaction, result } = await myWorkflow(req.scope).run()
-### listMyCustoms
+ const workflowEngineService = req.scope.resolve<
+ IWorkflowEngineService
+ >(
+ Modules.WORKFLOW_ENGINE
+ )
-### listMyCustoms
+ const subscriptionOptions = {
+ workflowId: "hello-world",
+ transactionId: transaction.transactionId,
+ subscriberId: "hello-world-subscriber",
+ }
-This method retrieves an array of records based on filters and pagination configurations.
+ await workflowEngineService.subscribe({
+ ...subscriptionOptions,
+ subscriber: async (data) => {
+ if (data.eventType === "onFinish") {
+ console.log("Finished execution", data.result)
+ // unsubscribe
+ await workflowEngineService.unsubscribe({
+ ...subscriptionOptions,
+ subscriberOrId: subscriptionOptions.subscriberId,
+ })
+ } else if (data.eventType === "onStepFailure") {
+ console.log("Workflow failed", data.step)
+ }
+ },
+ })
-For example:
+ res.send(result)
+}
+```
-```ts
-const myCustoms = await helloModuleService
- .listMyCustoms()
+In the above example, you execute the long-running workflow `hello-world` and resolve the Workflow Engine Module's main service from the Medusa container.
-// with filters
-const myCustoms = await helloModuleService
- .listMyCustoms({
- id: ["123"]
- })
-```
+### subscribe Method
-### listAndCount
+The main service's `subscribe` method allows you to listen to changes in the workflow execution’s status. It accepts an object having three properties:
-### retrieveMyCustom
+- workflowId: (\`string\`) The name of the workflow.
+- transactionId: (\`string\`) The ID of the workflow exection's transaction. The transaction's details are returned in the response of the workflow execution.
+- subscriberId: (\`string\`) The ID of the subscriber.
+- subscriber: (\`(data: \{ eventType: string, result?: any }) => Promise\\`) The function executed when the workflow execution's status changes. The function receives a data object. It has an \`eventType\` property, which you use to check the status of the workflow execution.
-This method retrieves a record by its ID.
+If the value of `eventType` in the `subscriber` function's first parameter is `onFinish`, the workflow finished executing. The first parameter then also has a `result` property holding the workflow's output.
-For example:
+### unsubscribe Method
-```ts
-const myCustom = await helloModuleService
- .retrieveMyCustom("123")
-```
+You can unsubscribe from the workflow using the workflow engine's `unsubscribe` method, which requires the same object parameter as the `subscribe` method.
-### retrieveMyCustom
+However, instead of the `subscriber` property, it requires a `subscriberOrId` property whose value is the same `subscriberId` passed to the `subscribe` method.
-### updateMyCustoms
+***
-This method updates and retrieves records of the data model.
+## Example: Restaurant-Delivery Recipe
-For example:
+To find a full example of a long-running workflow, refer to the [restaurant-delivery recipe](https://docs.medusajs.com/resources/recipes/marketplace/examples/restaurant-delivery/index.html.md).
-```ts
-const myCustom = await helloModuleService
- .updateMyCustoms({
- id: "123",
- name: "test"
- })
+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.
-// update multiple
-const myCustoms = await helloModuleService
- .updateMyCustoms([
- {
- id: "123",
- name: "test"
- },
- {
- id: "321",
- name: "test 2"
- },
- ])
-// use filters
-const myCustoms = await helloModuleService
- .updateMyCustoms([
- {
- selector: {
- id: ["123", "321"]
- },
- data: {
- name: "test"
- }
- },
- ])
-```
+# Retry Failed Steps
-### createMyCustoms
+In this chapter, you’ll learn how to configure steps to allow retrial on failure.
-### softDeleteMyCustoms
+## Configure a Step’s Retrial
-This method soft-deletes records using an array of IDs or an object of filters.
+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
-await helloModuleService.softDeleteMyCustoms("123")
+```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
-// soft-delete multiple
-await helloModuleService.softDeleteMyCustoms([
- "123", "321"
-])
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ },
+ async () => {
+ console.log("Executing step 1")
-// use filters
-await helloModuleService.softDeleteMyCustoms({
- id: ["123", "321"]
+ throw new Error("Oops! Something happened.")
+ }
+)
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
})
+
+export default myWorkflow
```
-### updateMyCustoms
+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.
-### deleteMyCustoms
+When you execute the above workflow, you’ll see the following result in the terminal:
-### softDeleteMyCustoms
+```bash
+Executing step 1
+Executing step 1
+Executing step 1
+error: Oops! Something happened.
+Error: Oops! Something happened.
+```
-### restoreMyCustoms
+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.
-### Using a Constructor
+***
-If you implement the `constructor` of your service, make sure to call `super` passing it `...arguments`.
+## Step Retry Intervals
-For example:
+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.
-```ts highlights={[["8"]]}
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
+For example:
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- constructor() {
- super(...arguments)
+```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ retryInterval: 2, // 2 seconds
+ },
+ async () => {
+ // ...
}
-}
-
-export default HelloModuleService
+)
```
+### Interval Changes Workflow to Long-Running
-# 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.
+By setting `retryInterval` on a step, a workflow becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. So, you won't receive its result or errors immediately when you execute the workflow.
-## numberOfExecutions Option
+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).
-The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime.
-For example:
+# Store Workflow Executions
-```ts highlights={highlights}
-export default async function myCustomJob() {
- console.log("I'll be executed three times only.")
-}
+In this chapter, you'll learn how to store workflow executions in the database and access them later.
-export const config = {
- name: "hello-world",
- // execute every minute
- schedule: "* * * * *",
- numberOfExecutions: 3,
-}
-```
+## Workflow Execution Retention
-The above scheduled job has the `numberOfExecutions` configuration set to `3`.
+Medusa doesn't store your workflow's execution details by default. However, you can configure a workflow to keep its execution details stored in the database.
-So, it'll only execute 3 times, each every minute, then it won't be executed anymore.
+This is useful for auditing and debugging purposes. When you store a workflow's execution, you can view details around its steps, their states and their output. You can also check whether the workflow or any of its steps failed.
-If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
+You can view stored workflow executions from the Medusa Admin dashboard by going to Settings -> Workflows.
+***
-# Access Workflow Errors
+## How to Store Workflow's Executions?
-In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
+### Prerequisites
-## How to Access Workflow Errors?
+- [Redis Workflow Engine must be installed and configured.](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md)
-By default, when an error occurs in a workflow, it throws that error, and the execution stops.
+`createWorkflow` from the Workflows SDK can accept an object as a first parameter to set the workflow's configuration. To enable storing a workflow's executions:
-You can configure the workflow to return the errors instead so that you can access and handle them differently.
+- Enable the `store` option. If your workflow is a [Long-Running Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md), this option is enabled by default.
+- Set the `retentionTime` option to the number of seconds that the workflow execution should be stored in the database.
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"
+```ts highlights={highlights}
+import { createStep, createWorkflow } from "@medusajs/framework/workflows-sdk"
-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),
- })
+const step1 = createStep(
+ {
+ name: "step-1",
+ },
+ async () => {
+ console.log("Hello from step 1")
}
+)
- res.send(result)
-}
-
+export const helloWorkflow = createWorkflow(
+ {
+ name: "hello-workflow",
+ retentionTime: 99999,
+ store: true,
+ },
+ () => {
+ step1()
+ }
+)
```
-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.
-
-
-# Expose a Workflow Hook
+Whenever you execute the `helloWorkflow` now, its execution details will be stored in the database.
-In this chapter, you'll learn how to expose a hook in your workflow.
+***
-## When to Expose a Hook
+## Retrieve Workflow Executions
-Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
+You can view stored workflow executions from the Medusa Admin dashboard by going to Settings -> Workflows.
-Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
+When you execute a workflow, the returned object has a `transaction` property containing the workflow execution's transaction details:
-***
+```ts
+const { transaction } = await helloWorkflow(container).run()
+```
-## How to Expose a Hook in a Workflow?
+To retrieve a workflow's execution details from the database, resolve the Workflow Engine Module from the container and use its `listWorkflowExecutions` method.
-To expose a hook in your workflow, use `createHook` from the Workflows SDK.
+For example, you can create a `GET` API Route at `src/workflows/[id]/route.ts` that retrieves a workflow execution for the specified transaction ID:
-For example:
+```ts title="src/workflows/[id]/route.ts" highlights={retrieveHighlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { Modules } from "@medusajs/framework/utils"
-```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 async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { transaction_id } = req.params
+
+ const workflowEngineService = req.scope.resolve(
+ Modules.WORKFLOW_ENGINE
+ )
-export const myWorkflow = createWorkflow(
- "my-workflow",
- function (input) {
- const product = createProductStep(input)
- const productCreatedHook = createHook(
- "productCreated",
- { productId: product.id }
- )
+ const [workflowExecution] = await workflowEngineService.listWorkflowExecutions({
+ transaction_id: transaction_id,
+ })
- return new WorkflowResponse(product, {
- hooks: [productCreatedHook],
- })
- }
-)
+ res.json({
+ workflowExecution,
+ })
+}
```
-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.
+In the above example, you resolve the Workflow Engine Module from the container and use its `listWorkflowExecutions` method, passing the `transaction_id` as a filter to retrieve its workflow execution details.
-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.
+A workflow execution object will be similar to the following:
-### How to Consume the Hook?
+```json
+{
+ "workflow_id": "hello-workflow",
+ "transaction_id": "01JJC2T6AVJCQ3N4BRD1EB88SP",
+ "id": "wf_exec_01JJC2T6B3P76JD35F12QTTA78",
+ "execution": {
+ "state": "done",
+ "steps": {},
+ "modelId": "hello-workflow",
+ "options": {},
+ "metadata": {},
+ "startedAt": 1737719880027,
+ "definition": {},
+ "timedOutAt": null,
+ "hasAsyncSteps": false,
+ "transactionId": "01JJC2T6AVJCQ3N4BRD1EB88SP",
+ "hasFailedSteps": false,
+ "hasSkippedSteps": false,
+ "hasWaitingSteps": false,
+ "hasRevertedSteps": false,
+ "hasSkippedOnFailureSteps": false
+ },
+ "context": {
+ "data": {},
+ "errors": []
+ },
+ "state": "done",
+ "created_at": "2025-01-24T09:58:00.036Z",
+ "updated_at": "2025-01-24T09:58:00.046Z",
+ "deleted_at": null
+}
+```
-To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
+### Example: Check if Stored Workflow Execution Failed
-```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
-import { myWorkflow } from "../my-workflow"
+To check if a stored workflow execution failed, you can check its `state` property:
-myWorkflow.hooks.productCreated(
- async ({ productId }, { container }) => {
- // TODO perform an action
- }
-)
+```ts
+if (workflowExecution.state === "failed") {
+ return res.status(500).json({
+ error: "Workflow failed",
+ })
+}
```
-The hook is available on the workflow's `hooks` property using its name `productCreated`.
+Other state values include `done`, `invoking`, and `compensating`.
-You invoke the hook, passing a step function (the hook handler) as a parameter.
+# Workflow Timeout
-# Compensation Function
+In this chapter, you’ll learn how to set a timeout for workflows and steps.
-In this chapter, you'll learn what a compensation function is and how to add it to a step.
+## What is a Workflow Timeout?
-## What is a Compensation Function
+By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
-A compensation function rolls back or undoes changes made by a step when an error occurs in the workflow.
+You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown.
-For example, if a step creates a record, the compensation function deletes the record when an error occurs later in the workflow.
+### Timeout Doesn't Stop Step Execution
-By using compensation functions, you provide a mechanism that guarantees data consistency in your application and across systems.
+Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result.
***
-## How to add a Compensation Function?
+## Configure Workflow Timeout
-A compensation function is passed as a second parameter to the `createStep` function.
+The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
-For example, create the file `src/workflows/hello-world.ts` with the following content:
+In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
-```ts title="src/workflows/hello-world.ts" highlights={[["15"], ["16"], ["17"]]} collapsibleLines="1-5" expandButtonLabel="Show Imports"
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More"
import {
- createStep,
- StepResponse,
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
} 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...")
+ // ...
}
)
+
+const myWorkflow = createWorkflow({
+ name: "hello-world",
+ timeout: 2, // 2 seconds
+}, function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+export default myWorkflow
+
```
-Each step can have a compensation function. The compensation function only runs if an error occurs throughout the workflow.
+This workflow's executions fail if they run longer than two seconds.
+
+A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`.
***
-## Test the Compensation Function
+## Configure Step Timeout
-Create a step in the same `src/workflows/hello-world.ts` file that throws an error:
+Alternatively, you can configure the timeout for a step rather than the entire workflow.
-```ts title="src/workflows/hello-world.ts"
-const step2 = createStep(
- "step-2",
+As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output.
+
+The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
+
+For example:
+
+```tsx
+const step1 = createStep(
+ {
+ name: "step-1",
+ timeout: 2, // 2 seconds
+ },
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()
+This step's executions fail if they run longer than two seconds.
- return new WorkflowResponse({
- message: str1,
- })
-})
+A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`.
-export default myWorkflow
-```
-Finally, execute the workflow from an API route:
+# Variable Manipulation in Workflows with transform
-```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"
+In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow.
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await myWorkflow(req.scope)
- .run()
+## Why Variable Manipulation isn't Allowed in Workflows
- res.send(result)
-}
-```
+Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps.
-Run the Medusa application and send a `GET` request to `/workflow`:
+At that point, variables in the workflow don't have any values. They only do when you execute the workflow.
-```bash
-curl http://localhost:9000/workflow
-```
+So, you can only pass variables as parameters to steps. But, in a workflow, you can't change a variable's value or, if the variable is an array, loop over its items.
-In the console, you'll see:
+Instead, use `transform` from the Workflows SDK.
-- `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.
+Restrictions for variable manipulation is only applicable in a workflow's definition. You can still manipulate variables in your step's code.
***
-## Pass Input to Compensation Function
+## What is the transform Utility?
-If a step creates a record, the compensation function must receive the ID of the record to remove it.
+`transform` creates a new variable as the result of manipulating other variables.
-To pass input to the compensation function, pass a second parameter in the `StepResponse` returned by the step.
+For example, consider you have two strings as the output of two steps:
-For example:
+```ts
+const str1 = step1()
+const str2 = step2()
+```
-```ts highlights={inputHighlights}
+To concatenate the strings, you create a new variable `str3` using the `transform` function:
+
+```ts highlights={highlights}
import {
- createStep,
- StepResponse,
+ createWorkflow,
+ WorkflowResponse,
+ transform,
} from "@medusajs/framework/workflows-sdk"
+// step imports...
-const step1 = createStep(
- "step-1",
- async () => {
- return new StepResponse(
- `Hello from step one!`,
- { message: "Oops! Rolling back my changes..." }
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ const str1 = step1(input)
+ const str2 = step2(input)
+
+ const str3 = transform(
+ { str1, str2 },
+ (data) => `${data.str1}${data.str2}`
)
- },
- async ({ message }) => {
- console.log(message)
+
+ return new WorkflowResponse(str3)
}
)
```
-In this example, the step passes an object as a second parameter to `StepResponse`.
+`transform` accepts two parameters:
-The compensation function receives the object and uses its `message` property to log a message.
+1. The first parameter is an object of variables to manipulate. The object is passed as a parameter to `transform`'s second parameter function.
+2. The second parameter is the function performing the variable manipulation.
+
+The value returned by the second parameter function is returned by `transform`. So, the `str3` variable holds the concatenated string.
+
+You can use the returned value in the rest of the workflow, either to pass it as an input to other steps or to return it in the workflow's response.
***
-## Resolve Resources from the Medusa Container
+## Example: Looping Over Array
-The compensation function receives an object second parameter. The object has a `container` property that you use to resolve resources from the Medusa container.
+Use `transform` to loop over arrays to create another variable from the array's items.
For example:
-```ts
+```ts collapsibleLines="1-7" expandButtonLabel="Show Imports"
import {
- createStep,
- StepResponse,
+ createWorkflow,
+ WorkflowResponse,
+ transform,
} from "@medusajs/framework/workflows-sdk"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+// step imports...
-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
+type WorkflowInput = {
+ items: {
+ id: string
+ name: string
+ }[]
+}
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function ({ items }: WorkflowInput) {
+ const ids = transform(
+ { items },
+ (data) => data.items.map((item) => item.id)
)
+
+ doSomethingStep(ids)
- logger.info(message)
+ // ...
}
)
```
-In this example, you use the `container` property in the second object parameter of the compensation function to resolve the logger.
+This workflow receives an `items` array in its input.
-You then use the logger to log a message.
+You use `transform` to create an `ids` variable, which is an array of strings holding the `id` of each item in the `items` array.
+
+You then pass the `ids` variable as a parameter to the `doSomethingStep`.
***
-## Handle Errors in Loops
+## Example: Creating a Date
-This feature is only available after [Medusa v2.0.5](https://github.com/medusajs/medusa/releases/tag/v2.0.5).
+If you create a date with `new Date()` in a workflow's constructor function, Medusa evaluates the date's value when it creates the internal representation of the workflow, not when the workflow is executed.
-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:
+So, use `transform` instead to create a date variable with `new Date()`.
+
+For example:
```ts
-// other imports...
-import { promiseAll } from "@medusajs/framework/utils"
+const myWorkflow = createWorkflow(
+ "hello-world",
+ () => {
+ const today = transform({}, () => new Date())
-type StepInput = {
- ids: string[]
-}
+ doSomethingStep(today)
+ }
+)
+```
-const step1 = createStep(
- "step-1",
- async ({ ids }: StepInput, { container }) => {
- const erpModuleService = container.resolve(
- ERP_MODULE
- )
- const prevData: unknown[] = []
+In this workflow, `today` is only evaluated when the workflow is executed.
- await promiseAll(
- ids.map(async (id) => {
- const data = await erpModuleService.retrieve(id)
+***
- await erpModuleService.delete(id)
+## Caveats
- prevData.push(id)
- })
+### Transform Evaluation
+
+`transform`'s value is only evaluated if you pass its output to a step or in the workflow response.
+
+For example, if you have the following workflow:
+
+```ts
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ const str = transform(
+ { input },
+ (data) => `${data.input.str1}${data.input.str2}`
)
- return new StepResponse(ids, prevData)
+ return new WorkflowResponse("done")
}
)
```
-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.
+Since `str`'s value isn't used as a step's input or passed to `WorkflowResponse`, its value is never evaluated.
-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:
+### Data Validation
-```ts highlights={highlights}
-try {
- await promiseAll(
- ids.map(async (id) => {
- const data = await erpModuleService.retrieve(id)
+`transform` should only be used to perform variable or data manipulation.
- await erpModuleService.delete(id)
+If you want to perform some validation on the data, use a step or [when-then](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md) instead.
- prevData.push(id)
- })
- )
-} catch (e) {
- return StepResponse.permanentFailure(
- `An error occurred: ${e}`,
- prevData
- )
-}
-```
+For example:
-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.
+```ts
+// DON'T
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ const str = transform(
+ { input },
+ (data) => {
+ if (!input.str1) {
+ throw new Error("Not allowed!")
+ }
+ }
+ )
+ }
+)
-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.
+// DO
+const validateHasStr1Step = createStep(
+ "validate-has-str1",
+ ({ input }) => {
+ if (!input.str1) {
+ throw new Error("Not allowed!")
+ }
+ }
+)
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function (input) {
+ validateHasStr1({
+ input,
+ })
-# Conditions in Workflows with When-Then
+ // workflow continues its execution only if
+ // the step doesn't throw the error.
+ }
+)
+```
-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?
+# Architectural Modules
-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.
+In this chapter, you’ll learn about architectural modules.
-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.
+## What is an Architectural Module?
-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.
+An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
-Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
+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.
***
-## How to use When-Then?
+## Architectural Module Types
-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.
+There are different architectural module types including:
-For example:
+
-```ts highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- when,
-} from "@medusajs/framework/workflows-sdk"
-// step imports...
+- 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.
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
+***
- const result = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- const stepResult = isActiveStep()
- return stepResult
- })
+## Architectural Modules List
- // executed without condition
- const anotherStepResult = anotherStep(result)
+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.
- return new WorkflowResponse(
- anotherStepResult
- )
- }
-)
-```
-In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
+# Workflow Hooks
-### When Parameters
+In this chapter, you'll learn what a workflow hook is and how to consume them.
-`when` accepts the following parameters:
+## What is a Workflow Hook?
-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`.
+A workflow hook is a point in a workflow where you can inject custom functionality as a step function, called a hook handler.
-### Then Parameters
+Medusa exposes hooks in many of its workflows that are used in its API routes. You can consume those hooks to add your custom logic.
-To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
+Refer to the [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) to view all workflows and their hooks.
-The callback function is only executed if `when`'s second parameter function returns a `true` value.
+You want to perform a custom action during a workflow's execution, such as when a product is created.
***
-## Implementing If-Else with When-Then
+## How to Consume a Hook?
-when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
+A workflow has a special `hooks` property which is an object that holds its hooks.
-For example:
+So, in a TypeScript or JavaScript file created under the `src/workflows/hooks` directory:
-```ts highlights={ifElseHighlights}
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
+- Import the workflow.
+- Access its hook using the `hooks` property.
+- Pass the hook a step function as a parameter to consume it.
- const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- return isActiveStep()
- })
+For example, to consume the `productsCreated` hook of Medusa's `createProductsWorkflow`, create the file `src/workflows/hooks/product-created.ts` with the following content:
- const notIsActiveResult = when(
- input,
- (input) => {
- return !input.is_active
- }
- ).then(() => {
- return notIsActiveStep()
- })
+```ts title="src/workflows/hooks/product-created.ts" highlights={handlerHighlights}
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
- // ...
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products }, { container }) => {
+ // TODO perform an action
}
)
```
-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.
-
-***
+The `productsCreated` hook is available on the workflow's `hooks` property by its name.
-## Specify Name for When-Then
+You invoke the hook, passing a step function (the hook handler) as a parameter.
-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:
+Now, when a product is created using the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), your hook handler is executed after the product is created.
-```ts
-const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
- }
-).then(() => {
- return isActiveStep()
-})
-```
+A hook can have only one handler.
-This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
+Refer to the [createProductsWorkflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) to see at which point the hook handler is executed.
-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.
+### Hook Handler Parameter
-You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
+Since a hook handler is essentially a step function, it receives the hook's input as a first parameter, and an object holding a `container` property as a second parameter.
-```ts highlights={nameHighlights}
-const { isActive } = when(
- "check-is-active",
- input,
- (input) => {
- return input.is_active
- }
-).then(() => {
- const isActive = isActiveStep()
+Each hook has different input. For example, the `productsCreated` hook receives an object having a `products` property holding the created product.
- return {
- isActive,
- }
-})
-```
+### Hook Handler Compensation
-Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
+Since the hook handler is a step function, you can set its compensation function as a second parameter of the hook.
-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`.
+For example:
-The second and third parameters are the same as the parameters you previously passed to `when`.
+```ts title="src/workflows/hooks/product-created.ts"
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products }, { container }) => {
+ // TODO perform an action
-# Execute Another Workflow
+ return new StepResponse(undefined, { ids })
+ },
+ async ({ ids }, { container }) => {
+ // undo the performed action
+ }
+)
+```
-In this chapter, you'll learn how to execute a workflow in another.
+The compensation function is executed if an error occurs in the workflow to undo the actions performed by the hook handler.
-## Execute in a Workflow
+The compensation function receives as an input the second parameter passed to the `StepResponse` returned by the step function.
-To execute a workflow in another, use the `runAsStep` method that every workflow has.
+It also accepts as a second parameter an object holding a `container` property to resolve resources from the Medusa container.
-For example:
+### Additional Data Property
-```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports"
-import {
- createWorkflow,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
+Medusa's workflows pass in the hook's input an `additional_data` property:
-const workflow = createWorkflow(
- "hello-world",
- async (input) => {
- const products = createProductsWorkflow.runAsStep({
- input: {
- products: [
- // ...
- ],
- },
- })
+```ts title="src/workflows/hooks/product-created.ts" highlights={[["4", "additional_data"]]}
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
- // ...
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products, additional_data }, { container }) => {
+ // TODO perform an action
}
)
```
-Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter.
+This property is an object that holds additional data passed to the workflow through the request sent to the API route using the workflow.
-The object has an `input` property to pass input to the workflow.
+Learn how to pass `additional_data` in requests to API routes in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
-***
+### Pass Additional Data to Workflow
-## Preparing Input Data
+You can also pass that additional data when executing the workflow. Pass it as a parameter to the `.run` method of the workflow:
-If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK.
+```ts title="src/workflows/hooks/product-created.ts" highlights={[["10", "additional_data"]]}
+import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
-Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
+export async function POST(req: MedusaRequest, res: MedusaResponse) {
+ await createProductsWorkflow(req.scope).run({
+ input: {
+ products: [
+ // ...
+ ],
+ additional_data: {
+ custom_field: "test",
+ },
+ },
+ })
+}
+```
-For example:
+Your hook handler then receives that passed data in the `additional_data` object.
-```ts highlights={transformHighlights} collapsibleLines="1-12"
-import {
- createWorkflow,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-type WorkflowInput = {
- title: string
-}
+# Commerce Modules
-const workflow = createWorkflow(
- "hello-product",
- async (input: WorkflowInput) => {
- const createProductsData = transform({
- input,
- }, (data) => [
- {
- title: `Hello ${data.input.title}`,
- },
- ])
+In this chapter, you'll learn about Medusa's commerce modules.
- const products = createProductsWorkflow.runAsStep({
- input: {
- products: createProductsData,
- },
- })
+## What is a Commerce Module?
- // ...
- }
-)
-```
+Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more.
-In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`.
+Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart.
+
+You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md)
+
+The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically.
+
+### List of Medusa's Commerce Modules
+
+Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa.
***
-## Run Workflow Conditionally
+## Use Commerce Modules in Custom Flows
-To run a workflow in another based on a condition, use when-then from the Workflows SDK.
+Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features.
-Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
+For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods:
-For example:
+```ts highlights={highlights}
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
-```ts highlights={whenHighlights} collapsibleLines="1-16"
-import {
- createWorkflow,
- when,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-import {
- CreateProductWorkflowInputDTO,
-} from "@medusajs/framework/types"
+export const countProductsStep = createStep(
+ "count-products",
+ async ({ }, { container }) => {
+ const productModuleService = container.resolve("product")
-type WorkflowInput = {
- product?: CreateProductWorkflowInputDTO
- should_create?: boolean
-}
+ const [,count] = await productModuleService.listAndCountProducts()
-const workflow = createWorkflow(
- "hello-product",
- async (input: WorkflowInput) => {
- const product = when(input, ({ should_create }) => should_create)
- .then(() => {
- return createProductsWorkflow.runAsStep({
- input: {
- products: [input.product],
- },
- })
- })
+ return new StepResponse(count)
}
)
```
-In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
-
+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.
-# Workflow Constraints
-This chapter lists constraints of defining a workflow or its steps.
+# Module Container
-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.
+In this chapter, you'll learn about the module's container and how to resolve resources in that container.
-This creates restrictions related to variable manipulations, using if-conditions, and other constraints. This chapter lists these constraints and provides their alternatives.
+Since modules are isolated, each module has a local container only used by the resources of that module.
-## Workflow Constraints
+So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
-### No Async Functions
+### List of Registered Resources
-The function passed to `createWorkflow` can’t be an async function:
+Find a list of resources or dependencies registered in a module's container in [this Development Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md).
-```ts highlights={[["4", "async", "Function can't be async."], ["11", "", "Correct way of defining the function."]]}
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- async function (input: WorkflowInput) {
- // ...
-})
+***
-// Do
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- // ...
-})
-```
+## Resolve Resources
-### No Direct Variable Manipulation
+### Services
-You can’t directly manipulate variables within the workflow's constructor function.
+A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
-Learn more about why you can't manipulate variables [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md)
+For example:
-Instead, use `transform` from the Workflows SDK:
+```ts highlights={[["4"], ["10"]]}
+import { Logger } from "@medusajs/framework/types"
-```ts highlights={highlights}
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const str1 = step1(input)
- const str2 = step2(input)
+type InjectedDependencies = {
+ logger: Logger
+}
- return new WorkflowResponse({
- message: `${str1}${str2}`,
- })
-})
+export default class HelloModuleService {
+ protected logger_: Logger
-// Do
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const str1 = step1(input)
- const str2 = step2(input)
+ constructor({ logger }: InjectedDependencies) {
+ this.logger_ = logger
- const result = transform(
- {
- str1,
- str2,
- },
- (input) => ({
- message: `${input.str1}${input.str2}`,
- })
- )
+ this.logger_.info("[HelloModuleService]: Hello World!")
+ }
- return new WorkflowResponse(result)
-})
+ // ...
+}
```
-### Create Dates in transform
-
-When you use `new Date()` in a workflow's constructor function, the date is evaluated when Medusa creates the internal representation of the workflow, not during execution.
-
-Instead, create the date using `transform`.
+### Loader
-Learn more about how Medusa creates an internal representation of a workflow [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md).
+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={dateHighlights}
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const today = new Date()
-
- return new WorkflowResponse({
- today,
- })
-})
+```ts highlights={[["9"]]}
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
-// Do
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const today = transform({}, () => new Date())
+export default async function helloWorldLoader({
+ container,
+}: LoaderOptions) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
- return new WorkflowResponse({
- today,
- })
-})
+ logger.info("[helloWorldLoader]: Hello, World!")
+}
```
-### No If Conditions
-You can't use if-conditions in a workflow.
+# Perform Database Operations in a Service
-Learn more about why you can't use if-conditions [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions#why-if-conditions-arent-allowed-in-workflows/index.html.md)
+In this chapter, you'll learn how to perform database operations in a module's service.
-Instead, use when-then from the Workflows SDK:
+This chapter is intended for more advanced database use-cases where you need more control over queries and operations. For basic database operations, such as creating or retrieving data of a model, use the [Service Factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) instead.
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- if (input.is_active) {
- // perform an action
- }
-})
+## Run Queries
-// Do (explained in the next chapter)
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- when(input, (input) => {
- return input.is_active
- })
- .then(() => {
- // perform an action
- })
-})
-```
+[MikroORM's entity manager](https://mikro-orm.io/docs/entity-manager) is a class that has methods to run queries on the database and perform operations.
-You can also pair multiple `when-then` blocks to implement an `if-else` condition as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md).
+Medusa provides an `InjectManager` decorator from the Modules SDK that injects a service's method with a [forked entity manager](https://mikro-orm.io/docs/identity-map#forking-entity-manager).
-### No Conditional Operators
+So, to run database queries in a service:
-You can't use conditional operators in a workflow, such as `??` or `||`.
+1. Add the `InjectManager` decorator to the method.
+2. Add as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator from the Modules SDK. This context holds database-related context, including the manager injected by `InjectManager`
-Learn more about why you can't use conditional operators [in this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions#why-if-conditions-arent-allowed-in-workflows/index.html.md)
+For example, in your service, add the following methods:
-Instead, use `transform` to store the desired value in a variable.
+```ts highlights={methodsHighlight}
+// other imports...
+import {
+ InjectManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { SqlEntityManager } from "@mikro-orm/knex"
-### Logical Or (||) Alternative
+class HelloModuleService {
+ // ...
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const message = input.message || "Hello"
-})
+ @InjectManager()
+ async getCount(
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ return await sharedContext.manager.count("my_custom")
+ }
+
+ @InjectManager()
+ async getCountSql(
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ const data = await sharedContext.manager.execute(
+ "SELECT COUNT(*) as num FROM my_custom"
+ )
+
+ return parseInt(data[0].num)
+ }
+}
+```
-// Do
-// other imports...
-import { transform } from "@medusajs/framework/workflows-sdk"
+You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator.
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const message = transform(
- {
- input,
- },
- (data) => data.input.message || "hello"
- )
-})
-```
+The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager).
-### Nullish Coalescing (??) Alternative
+You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count.
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const message = input.message ?? "Hello"
-})
-
-// Do
-// other imports...
-import { transform } from "@medusajs/framework/workflows-sdk"
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const message = transform(
- {
- input,
- },
- (data) => data.input.message ?? "hello"
- )
-})
-```
+Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
-### Double Not (!!) Alternative
+***
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- step1({
- isActive: !!input.is_active,
- })
-})
+## Execute Operations in Transactions
-// Do
-// other imports...
-import { transform } from "@medusajs/framework/workflows-sdk"
+To wrap database operations in a transaction, you create two methods:
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const isActive = transform(
- {
- input,
- },
- (data) => !!data.input.is_active
- )
-
- step1({
- isActive,
- })
-})
-```
+1. A private or protected method that's wrapped in a transaction. To wrap it in a transaction, you use the `InjectTransactionManager` decorator from the Modules SDK.
+2. A public method that calls the transactional method. You use on it the `InjectManager` decorator as explained in the previous section.
-### Ternary Alternative
+Both methods must accept as a last parameter an optional `sharedContext` parameter that has the `MedusaContext` decorator from the Modules SDK. It holds database-related contexts passed through the Medusa application.
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- step1({
- message: input.is_active ? "active" : "inactive",
- })
-})
+For example:
-// Do
-// other imports...
-import { transform } from "@medusajs/framework/workflows-sdk"
+```ts highlights={opHighlights}
+import {
+ InjectManager,
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const message = transform(
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ protected async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ const transactionManager = sharedContext.transactionManager
+ await transactionManager.nativeUpdate(
+ "my_custom",
{
- input,
+ id: input.id,
},
- (data) => {
- return data.input.is_active ? "active" : "inactive"
+ {
+ name: input.name,
}
)
-
- step1({
- message,
- })
-})
-```
-
-### Optional Chaining (?.) Alternative
-```ts
-// Don't
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- step1({
- name: input.customer?.name,
- })
-})
+ // retrieve again
+ const updatedRecord = await transactionManager.execute(
+ `SELECT * FROM my_custom WHERE id = '${input.id}'`
+ )
-// Do
-// other imports...
-import { transform } from "@medusajs/framework/workflows-sdk"
+ return updatedRecord
+ }
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input: WorkflowInput) {
- const name = transform(
- {
- input,
- },
- (data) => data.input.customer?.name
- )
-
- step1({
- name,
- })
-})
+ @InjectManager()
+ async update(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ) {
+ return await this.update_(input, sharedContext)
+ }
+}
```
-***
+The `HelloModuleService` has two methods:
-## Step Constraints
+- A protected `update_` that performs the database operations inside a transaction.
+- A public `update` that executes the transactional protected method.
-### Returned Values
+The shared context's `transactionManager` property holds the transactional entity manager (injected by `InjectTransactionManager`) that you use to perform database operations.
-A step must only return serializable values, such as [primitive values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) or an object.
+Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
-Values of other types, such as Maps, aren't allowed.
+### Why Wrap a Transactional Method
-```ts
-// Don't
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
+The variables in the transactional method (for example, `update_`) hold values that are uncommitted to the database. They're only committed once the method finishes execution.
-const step1 = createStep(
- "step-1",
- (input, { container }) => {
- const myMap = new Map()
+So, if in your method you perform database operations, then use their result to perform other actions, such as connecting to a third-party service, you'll be working with uncommitted data.
- // ...
+By placing only the database operations in a method that has the `InjectTransactionManager` and using it in a wrapper method, the wrapper method receives the committed result of the transactional method.
- return new StepResponse({
- myMap,
- })
- }
-)
+This is also useful if you perform heavy data normalization outside of the database operations. In that case, you don't hold the transaction for a longer time than needed.
-// Do
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
+For example, the `update` method could be changed to the following:
-const step1 = createStep(
- "step-1",
- (input, { container }) => {
- const myObj: Record = {}
+```ts
+// other imports...
+import { EntityManager } from "@mikro-orm/knex"
- // ...
+class HelloModuleService {
+ // ...
+ @InjectManager()
+ async update(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ) {
+ const newData = await this.update_(input, sharedContext)
- return new StepResponse({
- myObj,
- })
+ await sendNewDataToSystem(newData)
+
+ return newData
}
-)
+}
```
+In this case, only the `update_` method is wrapped in a transaction. The returned value `newData` holds the committed result, which can be used for other operations, such as passed to a `sendNewDataToSystem` method.
-# Long-Running Workflows
-
-In this chapter, you’ll learn what a long-running workflow is and how to configure it.
+### Using Methods in Transactional Methods
-## What is a Long-Running Workflow?
+If your transactional method uses other methods that accept a Medusa context, pass the shared context to those methods.
-When you execute a workflow, you wait until the workflow finishes execution to receive the output.
+For example:
-A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished.
+```ts
+// other imports...
+import { EntityManager } from "@mikro-orm/knex"
-### Why use Long-Running Workflows?
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ protected async anotherMethod(
+ @MedusaContext() sharedContext?: Context
+ ) {
+ // ...
+ }
+
+ @InjectTransactionManager()
+ protected async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ this.anotherMethod(sharedContext)
+ }
+}
+```
-Long-running workflows are useful if:
+You use the `anotherMethod` transactional method in the `update_` transactional method, so you pass it the shared context.
-- A task takes too long. For example, you're importing data from a CSV file.
-- The workflow's steps wait for an external action to finish before resuming execution. For example, before you import the data from the CSV file, you wait until the import is confirmed by the user.
+The `anotherMethod` now runs in the same transaction as the `update_` method.
***
-## Configure Long-Running Workflows
-
-A workflow is considered long-running if at least one step has its `async` configuration set to `true` and doesn't return a step response.
-
-For example, consider the following workflow and steps:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["15"]]} collapsibleLines="1-11" expandButtonLabel="Show More"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep("step-1", async () => {
- return new StepResponse({})
-})
-
-const step2 = createStep(
- {
- name: "step-2",
- async: true,
- },
- async () => {
- console.log("Waiting to be successful...")
- }
-)
+## Configure Transactions
-const step3 = createStep("step-3", async () => {
- return new StepResponse("Finished three steps")
-})
+To configure the transaction, such as its [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html), use the `baseRepository` dependency registered in your module's container.
-const myWorkflow = createWorkflow(
- "hello-world",
- function () {
- step1()
- step2()
- const message = step3()
+The `baseRepository` is an instance of a repository class that provides methods to create transactions, run database operations, and more.
- return new WorkflowResponse({
- message,
- })
-})
+The `baseRepository` has a `transaction` method that allows you to run a function within a transaction and configure that transaction.
-export default myWorkflow
-```
+For example, resolve the `baseRepository` in your service's constructor:
-The second step has in its configuration object `async` set to `true` and it doesn't return a step response. This indicates that this step is an asynchronous step.
+### Extending Service Factory
-So, when you execute the `hello-world` workflow, it continues its execution in the background once it reaches the second step.
+```ts highlights={[["14"]]}
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
+import { DAL } from "@medusajs/framework/types"
-A workflow is also considered long-running if one of its steps has their `retryInterval` option set as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/retry-failed-steps/index.html.md).
+type InjectedDependencies = {
+ baseRepository: DAL.RepositoryService
+}
-***
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ protected baseRepository_: DAL.RepositoryService
-## Change Step Status
+ constructor({ baseRepository }: InjectedDependencies) {
+ super(...arguments)
+ this.baseRepository_ = baseRepository
+ }
+}
-Once the workflow's execution reaches an async step, it'll wait in the background for the step to succeed or fail before it moves to the next step.
+export default HelloModuleService
+```
-To fail or succeed a step, use the Workflow Engine Module's main service that is registered in the Medusa Container under the `Modules.WORKFLOW_ENGINE` (or `workflowsModuleService`) key.
+### Without Service Factory
-### Retrieve Transaction ID
+```ts highlights={[["10"]]}
+import { DAL } from "@medusajs/framework/types"
-Before changing the status of a workflow execution's async step, you must have the execution's transaction ID.
+type InjectedDependencies = {
+ baseRepository: DAL.RepositoryService
+}
-When you execute the workflow, the object returned has a `transaction` property, which is an object that holds the details of the workflow execution's transaction. Use its `transactionId` to later change async steps' statuses:
+class HelloModuleService {
+ protected baseRepository_: DAL.RepositoryService
-```ts
-const { transaction } = await myWorkflow(req.scope)
- .run()
+ constructor({ baseRepository }: InjectedDependencies) {
+ this.baseRepository_ = baseRepository
+ }
+}
-// use transaction.transactionId later
+export default HelloModuleService
```
-### Change Step Status to Successful
-
-The Workflow Engine Module's main service has a `setStepSuccess` method to set a step's status to successful. If you use it on a workflow execution's async step, the workflow continues execution to the next step.
-
-For example, consider the following step:
+Then, add the following method that uses it:
-```ts highlights={successStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- Modules,
- TransactionHandlerType,
-} from "@medusajs/framework/utils"
+```ts highlights={repoHighlights}
+// ...
import {
- StepResponse,
- createStep,
-} from "@medusajs/framework/workflows-sdk"
+ InjectManager,
+ InjectTransactionManager,
+ MedusaContext,
+} from "@medusajs/framework/utils"
+import { Context } from "@medusajs/framework/types"
+import { EntityManager } from "@mikro-orm/knex"
-type SetStepSuccessStepInput = {
- transactionId: string
-};
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ protected async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ return await this.baseRepository_.transaction(
+ async (transactionManager) => {
+ await transactionManager.nativeUpdate(
+ "my_custom",
+ {
+ id: input.id,
+ },
+ {
+ name: input.name,
+ }
+ )
-export const setStepSuccessStep = createStep(
- "set-step-success-step",
- async function (
- { transactionId }: SetStepSuccessStepInput,
- { container }
- ) {
- const workflowEngineService = container.resolve(
- Modules.WORKFLOW_ENGINE
- )
+ // retrieve again
+ const updatedRecord = await transactionManager.execute(
+ `SELECT * FROM my_custom WHERE id = '${input.id}'`
+ )
- await workflowEngineService.setStepSuccess({
- idempotencyKey: {
- action: TransactionHandlerType.INVOKE,
- transactionId,
- stepId: "step-2",
- workflowId: "hello-world",
- },
- stepResponse: new StepResponse("Done!"),
- options: {
- container,
+ return updatedRecord
},
- })
+ {
+ transaction: sharedContext.transactionManager,
+ }
+ )
}
-)
-```
-
-In this step (which you use in a workflow other than the long-running workflow), you resolve the Workflow Engine Module's main service and set `step-2` of the previous workflow as successful.
-
-The `setStepSuccess` method of the workflow engine's main service accepts as a parameter an object having the following properties:
-- idempotencyKey: (\`object\`) The details of the workflow execution.
+ @InjectManager()
+ async update(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ) {
+ return await this.update_(input, sharedContext)
+ }
+}
+```
- - action: (\`invoke\` | \`compensate\`) If the step's compensation function is running, use \`compensate\`. Otherwise, use \`invoke\`.
+The `update_` method uses the `baseRepository_.transaction` method to wrap a function in a transaction.
- - transactionId: (\`string\`) The ID of the workflow execution's transaction.
+The function parameter receives a transactional entity manager as a parameter. Use it to perform the database operations.
- - stepId: (\`string\`) The ID of the step to change its status. This is the first parameter passed to \`createStep\` when creating the step.
+The `baseRepository_.transaction` method also receives as a second parameter an object of options. You must pass in it the `transaction` property and set its value to the `sharedContext.transactionManager` property so that the function wrapped in the transaction uses the injected transaction manager.
- - workflowId: (\`string\`) The ID of the workflow. This is the first parameter passed to \`createWorkflow\` when creating the workflow.
-- stepResponse: (\`StepResponse\`) Set the response of the step. This is similar to the response you return in a step's definition, but since the \`async\` step doesn't have a response, you set its response when changing its status.
-- options: (\`Record\\`) Options to pass to the step.
+Refer to [MikroORM's reference](https://mikro-orm.io/api/knex/class/EntityManager) for a full list of the entity manager's methods.
- - container: (\`MedusaContainer\`) An instance of the Medusa Container
+### Transaction Options
-### Change Step Status to Failed
+The second parameter of the `baseRepository_.transaction` method is an object of options that accepts the following properties:
-The Workflow Engine Module's main service also has a `setStepFailure` method that changes a step's status to failed. It accepts the same parameter as `setStepSuccess`.
+1. `transaction`: Set the transactional entity manager passed to the function. You must provide this option as explained in the previous section.
-After changing the async step's status to failed, the workflow execution fails and the compensation functions of previous steps are executed.
+```ts highlights={[["16"]]}
+// other imports...
+import { EntityManager } from "@mikro-orm/knex"
-For example:
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ return await this.baseRepository_.transaction(
+ async (transactionManager) => {
+ // ...
+ },
+ {
+ transaction: sharedContext.transactionManager,
+ }
+ )
+ }
+}
+```
-```ts highlights={failureStatusHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- Modules,
- TransactionHandlerType,
-} from "@medusajs/framework/utils"
-import {
- StepResponse,
- createStep,
-} from "@medusajs/framework/workflows-sdk"
+2. `isolationLevel`: Sets the transaction's [isolation level](https://www.postgresql.org/docs/current/transaction-iso.html). Its values can be:
+ - `read committed`
+ - `read uncommitted`
+ - `snapshot`
+ - `repeatable read`
+ - `serializable`
-type SetStepFailureStepInput = {
- transactionId: string
-};
+```ts highlights={[["19"]]}
+// other imports...
+import { IsolationLevel } from "@mikro-orm/core"
-export const setStepFailureStep = createStep(
- "set-step-success-step",
- async function (
- { transactionId }: SetStepFailureStepInput,
- { container }
- ) {
- const workflowEngineService = container.resolve(
- Modules.WORKFLOW_ENGINE
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ return await this.baseRepository_.transaction(
+ async (transactionManager) => {
+ // ...
+ },
+ {
+ isolationLevel: IsolationLevel.READ_COMMITTED,
+ }
)
+ }
+}
+```
- await workflowEngineService.setStepFailure({
- idempotencyKey: {
- action: TransactionHandlerType.INVOKE,
- transactionId,
- stepId: "step-2",
- workflowId: "hello-world",
- },
- stepResponse: new StepResponse("Failed!"),
- options: {
- container,
+3. `enableNestedTransactions`: (default: `false`) whether to allow using nested transactions.
+ - If `transaction` is provided and this is disabled, the manager in `transaction` is re-used.
+
+```ts highlights={[["16"]]}
+class HelloModuleService {
+ // ...
+ @InjectTransactionManager()
+ async update_(
+ input: {
+ id: string,
+ name: string
+ },
+ @MedusaContext() sharedContext?: Context
+ ): Promise {
+ return await this.baseRepository_.transaction(
+ async (transactionManager) => {
+ // ...
},
- })
+ {
+ enableNestedTransactions: false,
+ }
+ )
}
-)
+}
```
-You use this step in another workflow that changes the status of an async step in a long-running workflow's execution to failed.
-***
+# Loaders
-## Access Long-Running Workflow Status and Result
+In this chapter, you’ll learn about loaders and how to use them.
-To access the status and result of a long-running workflow execution, use the `subscribe` and `unsubscribe` methods of the Workflow Engine Module's main service.
+## What is a Loader?
-To retrieve the workflow execution's details at a later point, you must enable [storing the workflow's executions](https://docs.medusajs.com/learn/fundamentals/workflows/store-executions/index.html.md).
+When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup.
-For example:
+In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules.
-```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-11" expandButtonLabel="Show Imports"
-import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-import myWorkflow from "../../../workflows/hello-world"
-import {
- IWorkflowEngineService,
-} from "@medusajs/framework/types"
-import { Modules } from "@medusajs/framework/utils"
+Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module.
-export async function GET(req: MedusaRequest, res: MedusaResponse) {
- const { transaction, result } = await myWorkflow(req.scope).run()
+Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md).
- const workflowEngineService = req.scope.resolve<
- IWorkflowEngineService
- >(
- Modules.WORKFLOW_ENGINE
- )
+***
- const subscriptionOptions = {
- workflowId: "hello-world",
- transactionId: transaction.transactionId,
- subscriberId: "hello-world-subscriber",
- }
+## How to Create a Loader?
- await workflowEngineService.subscribe({
- ...subscriptionOptions,
- subscriber: async (data) => {
- if (data.eventType === "onFinish") {
- console.log("Finished execution", data.result)
- // unsubscribe
- await workflowEngineService.unsubscribe({
- ...subscriptionOptions,
- subscriberOrId: subscriptionOptions.subscriberId,
- })
- } else if (data.eventType === "onStepFailure") {
- console.log("Workflow failed", data.step)
- }
- },
- })
+### 1. Implement Loader Function
- res.send(result)
-}
-```
+You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory.
-In the above example, you execute the long-running workflow `hello-world` and resolve the Workflow Engine Module's main service from the Medusa container.
+For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content:
-### subscribe Method
+
-The main service's `subscribe` method allows you to listen to changes in the workflow execution’s status. It accepts an object having three properties:
+Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-- workflowId: (\`string\`) The name of the workflow.
-- transactionId: (\`string\`) The ID of the workflow exection's transaction. The transaction's details are returned in the response of the workflow execution.
-- subscriberId: (\`string\`) The ID of the subscriber.
-- subscriber: (\`(data: \{ eventType: string, result?: any }) => Promise\\`) The function executed when the workflow execution's status changes. The function receives a data object. It has an \`eventType\` property, which you use to check the status of the workflow execution.
+```ts title="src/modules/hello/loaders/hello-world.ts"
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
-If the value of `eventType` in the `subscriber` function's first parameter is `onFinish`, the workflow finished executing. The first parameter then also has a `result` property holding the workflow's output.
+export default async function helloWorldLoader({
+ container,
+}: LoaderOptions) {
+ const logger = container.resolve("logger")
-### unsubscribe Method
+ logger.info("[helloWorldLoader]: Hello, World!")
+}
+```
-You can unsubscribe from the workflow using the workflow engine's `unsubscribe` method, which requires the same object parameter as the `subscribe` method.
+The loader file exports an async function, which is the function executed when the application loads.
-However, instead of the `subscriber` property, it requires a `subscriberOrId` property whose value is the same `subscriberId` passed to the `subscribe` method.
+The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal.
-***
+Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md).
-## Example: Restaurant-Delivery Recipe
+### 2. Export Loader in Module Definition
-To find a full example of a long-running workflow, refer to the [restaurant-delivery recipe](https://docs.medusajs.com/resources/recipes/marketplace/examples/restaurant-delivery/index.html.md).
+After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it.
-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.
+So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`:
+```ts title="src/modules/hello/index.ts"
+// other imports...
+import helloWorldLoader from "./loaders/hello-world"
-# Multiple Step Usage in Workflow
+export default Module("hello", {
+ // ...
+ loaders: [helloWorldLoader],
+})
+```
-In this chapter, you'll learn how to use a step multiple times in a workflow.
+The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts.
-## Problem Reusing a Step in a Workflow
+### Test the Loader
-In some cases, you may need to use a step multiple times in the same workflow.
+Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application:
-The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products.
+```bash npm2yarn
+npm run dev
+```
-Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step:
+Then, you'll find the following message logged in the terminal:
-```ts
-const useQueryGraphStep = createStep(
- "use-query-graph"
- // ...
-)
+```plain
+info: [HELLO MODULE] Just started the Medusa application!
```
-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:
+This indicates that the loader in the `hello` module ran and logged this message.
-```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"],
- })
- }
-)
-```
+## Example: Register Custom MongoDB Connection
-The next section explains how to fix this issue to use the same step multiple times in a workflow.
+As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module.
-***
+Consider your have a MongoDB module that allows you to perform operations on a MongoDB database.
-## How to Use a Step Multiple Times in a Workflow?
+### Prerequisites
-When you execute a step in a workflow, you can chain a `config` method to it to change the step's config.
+- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com)
+- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver)
-Use the `config` method to change a step's ID for a single execution.
+To connect to the database, you create the following loader in your module:
-So, this is the correct way to write the example above:
+```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
+import { LoaderOptions } from "@medusajs/framework/types"
+import { asValue } from "awilix"
+import { MongoClient } from "mongodb"
-```ts highlights={highlights}
-const helloWorkflow = createWorkflow(
- "hello",
- () => {
- const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: ["id"],
- })
+type ModuleOptions = {
+ connection_url?: string
+ db_name?: string
+}
- // ✓ No error occurs, the step has a different ID.
- const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: ["id"],
- }).config({ name: "fetch-customers" })
+export default async function mongoConnectionLoader({
+ container,
+ options,
+}: LoaderOptions) {
+ if (!options.connection_url) {
+ throw new Error(`[MONGO MDOULE]: connection_url option is required.`)
}
-)
+ if (!options.db_name) {
+ throw new Error(`[MONGO MDOULE]: db_name option is required.`)
+ }
+ const logger = container.resolve("logger")
+
+ try {
+ const clientDb = (
+ await (new MongoClient(options.connection_url)).connect()
+ ).db(options.db_name)
+
+ logger.info("Connected to MongoDB")
+
+ container.register(
+ "mongoClient",
+ asValue(clientDb)
+ )
+ } catch (e) {
+ logger.error(
+ `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}`
+ )
+ }
+}
```
-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 loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example:
-The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`.
+```ts title="medusa-config.ts" highlights={optionHighlights}
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/mongo",
+ options: {
+ connection_url: process.env.MONGO_CONNECTION_URL,
+ db_name: process.env.MONGO_DB_NAME,
+ },
+ },
+ ],
+})
+```
+Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options:
-# Retry Failed Steps
+- `connection_url`: the URL to connect to the MongoDB database.
+- `db_name`: The name of the database to connect to.
-In this chapter, you’ll learn how to configure steps to allow retrial on failure.
+In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options.
-## Configure a Step’s Retrial
+After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters:
-By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
+1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client.
+2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
-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.
+### Use Custom Registered Resource in Module's Service
+
+After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service.
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"
+```ts title="src/modules/mongo/service.ts"
+import type { Db } from "mongodb"
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- },
- async () => {
- console.log("Executing step 1")
+type InjectedDependencies = {
+ mongoClient: Db
+}
- throw new Error("Oops! Something happened.")
+export default class MongoModuleService {
+ private mongoClient_: Db
+
+ constructor({ mongoClient }: InjectedDependencies) {
+ this.mongoClient_ = mongoClient
}
-)
-const myWorkflow = createWorkflow(
- "hello-world",
- function () {
- const str1 = step1()
+ async createMovie({ title }: {
+ title: string
+ }) {
+ const moviesCol = this.mongoClient_.collection("movie")
- return new WorkflowResponse({
- message: str1,
- })
-})
+ const insertedMovie = await moviesCol.insertOne({
+ title,
+ })
-export default myWorkflow
-```
+ const movie = await moviesCol.findOne({
+ _id: insertedMovie.insertedId,
+ })
-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.
+ return movie
+ }
-When you execute the above workflow, you’ll see the following result in the terminal:
+ async deleteMovie(id: string) {
+ const moviesCol = this.mongoClient_.collection("movie")
-```bash
-Executing step 1
-Executing step 1
-Executing step 1
-error: Oops! Something happened.
-Error: Oops! Something happened.
+ await moviesCol.deleteOne({
+ _id: {
+ equals: id,
+ },
+ })
+ }
+}
```
-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.
+The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively.
-***
+Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module:
-## Step Retry Intervals
+```ts title="src/modules/mongo/index.ts" highlights={[["9"]]}
+import { Module } from "@medusajs/framework/utils"
+import MongoModuleService from "./service"
+import mongoConnectionLoader from "./loaders/connection"
-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.
+export const MONGO_MODULE = "mongo"
-For example:
+export default Module(MONGO_MODULE, {
+ service: MongoModuleService,
+ loaders: [mongoConnectionLoader],
+})
+```
-```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- retryInterval: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
+### Test it Out
+
+You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal:
+
+```bash
+info: Connected to MongoDB
```
-### Interval Changes Workflow to Long-Running
+You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.
-By setting `retryInterval` on a step, a workflow becomes a [long-running workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md) that runs asynchronously in the background. So, 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).
+# Module Isolation
+In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
-# Store Workflow Executions
+- 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.
-In this chapter, you'll learn how to store workflow executions in the database and access them later.
+## How are Modules Isolated?
-## Workflow Execution Retention
+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.
-Medusa doesn't store your workflow's execution details by default. However, you can configure a workflow to keep its execution details stored in the database.
+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.
-This is useful for auditing and debugging purposes. When you store a workflow's execution, you can view details around its steps, their states and their output. You can also check whether the workflow or any of its steps failed.
+***
-You can view stored workflow executions from the Medusa Admin dashboard by going to Settings -> Workflows.
+## 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 Store Workflow's Executions?
+## How to Extend Data Model of Another Module?
-### Prerequisites
+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).
-- [Redis Workflow Engine must be installed and configured.](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md)
+***
-`createWorkflow` from the Workflows SDK can accept an object as a first parameter to set the workflow's configuration. To enable storing a workflow's executions:
+## How to Use Services of Other Modules?
-- Enable the `store` option. If your workflow is a [Long-Running Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/long-running-workflow/index.html.md), this option is enabled by default.
-- Set the `retentionTime` option to the number of seconds that the workflow execution should be stored in the database.
+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.
-For example:
+Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
-```ts highlights={highlights}
-import { createStep, createWorkflow } from "@medusajs/framework/workflows-sdk"
+### Example
-const step1 = createStep(
- {
- name: "step-1",
- },
- async () => {
- console.log("Hello from step 1")
+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)
}
)
-export const helloWorkflow = createWorkflow(
- {
- name: "hello-workflow",
- retentionTime: 99999,
- store: true,
+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",
() => {
- step1()
+ const brands = retrieveBrandsStep()
+
+ createBrandsInCmsStep({ brands })
}
)
```
-Whenever you execute the `helloWorkflow` now, its execution details will be stored in the database.
+You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
-***
-## Retrieve Workflow Executions
+# Modules Directory Structure
-You can view stored workflow executions from the Medusa Admin dashboard by going to Settings -> Workflows.
+In this document, you'll learn about the expected files and directories in your module.
-When you execute a workflow, the returned object has a `transaction` property containing the workflow execution's transaction details:
+
-```ts
-const { transaction } = await helloWorkflow(container).run()
-```
+## index.ts
-To retrieve a workflow's execution details from the database, resolve the Workflow Engine Module from the container and use its `listWorkflowExecutions` method.
+The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-For example, you can create a `GET` API Route at `src/workflows/[id]/route.ts` that retrieves a workflow execution for the specified transaction ID:
+***
-```ts title="src/workflows/[id]/route.ts" highlights={retrieveHighlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { Modules } from "@medusajs/framework/utils"
+## service.ts
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { transaction_id } = req.params
-
- const workflowEngineService = req.scope.resolve(
- Modules.WORKFLOW_ENGINE
- )
-
- const [workflowExecution] = await workflowEngineService.listWorkflowExecutions({
- transaction_id: transaction_id,
- })
-
- res.json({
- workflowExecution,
- })
-}
-```
+A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-In the above example, you resolve the Workflow Engine Module from the container and use its `listWorkflowExecutions` method, passing the `transaction_id` as a filter to retrieve its workflow execution details.
+***
-A workflow execution object will be similar to the following:
+## Other Directories
-```json
-{
- "workflow_id": "hello-workflow",
- "transaction_id": "01JJC2T6AVJCQ3N4BRD1EB88SP",
- "id": "wf_exec_01JJC2T6B3P76JD35F12QTTA78",
- "execution": {
- "state": "done",
- "steps": {},
- "modelId": "hello-workflow",
- "options": {},
- "metadata": {},
- "startedAt": 1737719880027,
- "definition": {},
- "timedOutAt": null,
- "hasAsyncSteps": false,
- "transactionId": "01JJC2T6AVJCQ3N4BRD1EB88SP",
- "hasFailedSteps": false,
- "hasSkippedSteps": false,
- "hasWaitingSteps": false,
- "hasRevertedSteps": false,
- "hasSkippedOnFailureSteps": false
- },
- "context": {
- "data": {},
- "errors": []
- },
- "state": "done",
- "created_at": "2025-01-24T09:58:00.036Z",
- "updated_at": "2025-01-24T09:58:00.046Z",
- "deleted_at": null
-}
-```
+The following directories are optional and their content are explained more in the following chapters:
-### Example: Check if Stored Workflow Execution Failed
+- `models`: Holds the data models representing tables in the database.
+- `migrations`: Holds the migration files used to reflect changes on the database.
+- `loaders`: Holds the scripts to run on the Medusa application's start-up.
-To check if a stored workflow execution failed, you can check its `state` property:
-```ts
-if (workflowExecution.state === "failed") {
- return res.status(500).json({
- error: "Workflow failed",
- })
-}
-```
+# Multiple Services in a Module
-Other state values include `done`, `invoking`, and `compensating`.
+In this chapter, you'll learn how to use multiple services in a module.
+## Module's Main and Internal Services
-# Run Workflow Steps in Parallel
+A module has one main service only, which is the service exported in the module's definition.
-In this chapter, you’ll learn how to run workflow steps in parallel.
+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.
-## 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.
+## How to Add an Internal Service
-The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
+### 1. Create Service
-For example:
+To add an internal service, create it in the `services` directory of your module.
-```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"
+For example, create the file `src/modules/hello/services/client.ts` with the following content:
-interface WorkflowInput {
- title: string
+```ts title="src/modules/hello/services/client.ts"
+export class ClientService {
+ async getMessage(): Promise {
+ return "Hello, World!"
+ }
}
+```
-const myWorkflow = createWorkflow(
- "my-workflow",
- (input: WorkflowInput) => {
- const product = createProductStep(input)
+### 2. Export Service in Index
- const [prices, productSalesChannel] = parallelize(
- createPricesStep(product),
- attachProductToSalesChannelStep(product)
- )
+Next, create an `index.ts` file under the `services` directory of the module that exports your internal services.
- const id = product.id
- const refetchedProduct = getProductStep(product.id)
+For example, create the file `src/modules/hello/services/index.ts` with the following content:
- return new WorkflowResponse(refetchedProduct)
- }
-)
+```ts title="src/modules/hello/services/index.ts"
+export * from "./client"
```
-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`.
+This exports the `ClientService`.
+### 3. Resolve Internal Service
-# Workflow Hooks
+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.
-In this chapter, you'll learn what a workflow hook is and how to consume them.
+For example, in your main service:
-## What is a Workflow Hook?
+```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]}
+// other imports...
+import { ClientService } from "./services"
-A workflow hook is a point in a workflow where you can inject custom functionality as a step function, called a hook handler.
+type InjectedDependencies = {
+ clientService: ClientService
+}
-Medusa exposes hooks in many of its workflows that are used in its API routes. You can consume those hooks to add your custom logic.
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ protected clientService_: ClientService
-Refer to the [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) to view all workflows and their hooks.
+ constructor({ clientService }: InjectedDependencies) {
+ super(...arguments)
+ this.clientService_ = clientService
+ }
+}
+```
-You want to perform a custom action during a workflow's execution, such as when a product is created.
+You can now use your internal service in your main service.
***
-## How to Consume a Hook?
+## Resolve Resources in Internal Service
-A workflow has a special `hooks` property which is an object that holds its hooks.
+Resolve dependencies from your module's container in the constructor of your internal service.
-So, in a TypeScript or JavaScript file created under the `src/workflows/hooks` directory:
+For example:
-- Import the workflow.
-- Access its hook using the `hooks` property.
-- Pass the hook a step function as a parameter to consume it.
+```ts
+import { Logger } from "@medusajs/framework/types"
-For example, to consume the `productsCreated` hook of Medusa's `createProductsWorkflow`, create the file `src/workflows/hooks/product-created.ts` with the following content:
+type InjectedDependencies = {
+ logger: Logger
+}
-```ts title="src/workflows/hooks/product-created.ts" highlights={handlerHighlights}
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+export class ClientService {
+ protected logger_: Logger
-createProductsWorkflow.hooks.productsCreated(
- async ({ products }, { container }) => {
- // TODO perform an action
+ constructor({ logger }: InjectedDependencies) {
+ this.logger_ = logger
}
-)
+}
```
-The `productsCreated` hook is available on the workflow's `hooks` property by its name.
-
-You invoke the hook, passing a step function (the hook handler) as a parameter.
-
-Now, when a product is created using the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), your hook handler is executed after the product is created.
-
-A hook can have only one handler.
-
-Refer to the [createProductsWorkflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) to see at which point the hook handler is executed.
+***
-### Hook Handler Parameter
+## Access Module Options
-Since a hook handler is essentially a step function, it receives the hook's input as a first parameter, and an object holding a `container` property as a second parameter.
+Your internal service can't access the module's options.
-Each hook has different input. For example, the `productsCreated` hook receives an object having a `products` property holding the created product.
+To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`.
-### Hook Handler Compensation
+For example:
-Since the hook handler is a step function, you can set its compensation function as a second parameter of the hook.
+```ts
+import { ConfigModule } from "@medusajs/framework/types"
+import { HELLO_MODULE } from ".."
-For example:
+export type InjectedDependencies = {
+ configModule: ConfigModule
+}
-```ts title="src/workflows/hooks/product-created.ts"
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+export class ClientService {
+ protected options: Record
-createProductsWorkflow.hooks.productsCreated(
- async ({ products }, { container }) => {
- // TODO perform an action
+ constructor({ configModule }: InjectedDependencies) {
+ const moduleDef = configModule.modules[HELLO_MODULE]
- return new StepResponse(undefined, { ids })
- },
- async ({ ids }, { container }) => {
- // undo the performed action
+ if (typeof moduleDef !== "boolean") {
+ this.options = moduleDef.options
+ }
}
-)
+}
```
-The compensation function is executed if an error occurs in the workflow to undo the actions performed by the hook handler.
+The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key.
-The compensation function receives as an input the second parameter passed to the `StepResponse` returned by the step function.
+If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
-It also accepts as a second parameter an object holding a `container` property to resolve resources from the Medusa container.
-### Additional Data Property
+# Service Constraints
-Medusa's workflows pass in the hook's input an `additional_data` property:
+This chapter lists constraints to keep in mind when creating a service.
-```ts title="src/workflows/hooks/product-created.ts" highlights={[["4", "additional_data"]]}
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+## Use Async Methods
-createProductsWorkflow.hooks.productsCreated(
- async ({ products, additional_data }, { container }) => {
- // TODO perform an action
- }
-)
-```
+Medusa wraps service method executions to inject useful context or transactions. However, since Medusa can't detect whether the method is asynchronous, it always executes methods in the wrapper with the `await` keyword.
-This property is an object that holds additional data passed to the workflow through the request sent to the API route using the workflow.
+For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method:
-Learn how to pass `additional_data` in requests to API routes in [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md).
+```ts
+await helloModuleService.getMessage()
+```
-### Pass Additional Data to Workflow
+So, make sure your service's methods are always async to avoid unexpected errors or behavior.
-You can also pass that additional data when executing the workflow. Pass it as a parameter to the `.run` method of the workflow:
+```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]}
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
-```ts title="src/workflows/hooks/product-created.ts" highlights={[["10", "additional_data"]]}
-import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
-import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ // Don't
+ getMessage(): string {
+ return "Hello, World!"
+ }
-export async function POST(req: MedusaRequest, res: MedusaResponse) {
- await createProductsWorkflow(req.scope).run({
- input: {
- products: [
- // ...
- ],
- additional_data: {
- custom_field: "test",
- },
- },
- })
+ // Do
+ async getMessage(): Promise {
+ return "Hello, World!"
+ }
}
+
+export default HelloModuleService
```
-Your hook handler then receives that passed data in the `additional_data` object.
+# Module Options
-# Variable Manipulation in Workflows with transform
+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.
-In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow.
+## What are Module Options?
-## Why Variable Manipulation isn't Allowed in Workflows
+A module can receive options to customize or configure its functionality. For example, if you’re creating a module that integrates a third-party service, you’ll want to receive the integration credentials in the options rather than adding them directly in your code.
-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.
+## How to Pass Options to a Module?
-So, you can only pass variables as parameters to steps. But, in a workflow, you can't change a variable's value or, if the variable is an array, loop over its items.
+To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`.
-Instead, use `transform` from the Workflows SDK.
+For example:
-Restrictions for variable manipulation is only applicable in a workflow's definition. You can still manipulate variables in your step's code.
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/hello",
+ options: {
+ capitalize: true,
+ },
+ },
+ ],
+})
+```
-***
+The `options` property’s value is an object. You can pass any properties you want.
-## What is the transform Utility?
+### Pass Options to a Module in a Plugin
-`transform` creates a new variable as the result of manipulating other variables.
+If your module is part of a plugin, you can pass options to the module in the plugin’s configuration.
-For example, consider you have two strings as the output of two steps:
+For example:
-```ts
-const str1 = step1()
-const str2 = step2()
+```ts title="medusa-config.ts"
+import { defineConfig } from "@medusajs/framework/utils"
+module.exports = defineConfig({
+ plugins: [
+ {
+ resolve: "@myorg/plugin-name",
+ options: {
+ capitalize: true,
+ },
+ },
+ ],
+})
```
-To concatenate the strings, you create a new variable `str3` using the `transform` function:
+The `options` property in the plugin configuration is passed to all modules in a plugin.
-```ts highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-// step imports...
+***
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input) {
- const str1 = step1(input)
- const str2 = step2(input)
+## Access Module Options in Main Service
- const str3 = transform(
- { str1, str2 },
- (data) => `${data.str1}${data.str2}`
- )
+The module’s main service receives the module options as a second parameter.
- return new WorkflowResponse(str3)
- }
-)
-```
+For example:
-`transform` accepts two parameters:
+```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
-1. The first parameter is an object of variables to manipulate. The object is passed as a parameter to `transform`'s second parameter function.
-2. The second parameter is the function performing the variable manipulation.
+// recommended to define type in another file
+type ModuleOptions = {
+ capitalize?: boolean
+}
-The value returned by the second parameter function is returned by `transform`. So, the `str3` variable holds the concatenated string.
+export default class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ protected options_: ModuleOptions
-You can use the returned value in the rest of the workflow, either to pass it as an input to other steps or to return it in the workflow's response.
+ constructor({}, options?: ModuleOptions) {
+ super(...arguments)
+
+ this.options_ = options || {
+ capitalize: false,
+ }
+ }
+
+ // ...
+}
+```
***
-## Example: Looping Over Array
+## Access Module Options in Loader
-Use `transform` to loop over arrays to create another variable from the array's items.
+The object that a module’s loaders receive as a parameter has an `options` property holding the module's options.
For example:
-```ts collapsibleLines="1-7" expandButtonLabel="Show Imports"
-import {
- createWorkflow,
- WorkflowResponse,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-// step imports...
+```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]}
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
-type WorkflowInput = {
- items: {
- id: string
- name: string
- }[]
+// recommended to define type in another file
+type ModuleOptions = {
+ capitalize?: boolean
}
-const myWorkflow = createWorkflow(
- "hello-world",
- function ({ items }: WorkflowInput) {
- const ids = transform(
- { items },
- (data) => data.items.map((item) => item.id)
- )
-
- doSomethingStep(ids)
-
- // ...
- }
-)
+export default async function helloWorldLoader({
+ options,
+}: LoaderOptions) {
+
+ console.log(
+ "[HELLO MODULE] Just started the Medusa application!",
+ options
+ )
+}
```
-This workflow receives an `items` array in its input.
-
-You use `transform` to create an `ids` variable, which is an array of strings holding the `id` of each item in the `items` array.
-
-You then pass the `ids` variable as a parameter to the `doSomethingStep`.
-
***
-## Example: Creating a Date
+## Validate Module Options
-If you create a date with `new Date()` in a workflow's constructor function, Medusa evaluates the date's value when it creates the internal representation of the workflow, not when the workflow is executed.
+If you expect a certain option and want to throw an error if it's not provided or isn't valid, it's recommended to perform the validation in a loader. The module's service is only instantiated when it's used, whereas the loader runs the when the Medusa application starts.
-So, use `transform` instead to create a date variable with `new Date()`.
+So, by performing the validation in the loader, you ensure you can throw an error at an early point, rather than when the module is used.
-For example:
+For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`:
-```ts
-const myWorkflow = createWorkflow(
- "hello-world",
- () => {
- const today = transform({}, () => new Date())
+```ts title="src/modules/hello/loaders/validate.ts"
+import { LoaderOptions } from "@medusajs/framework/types"
+import { MedusaError } from "@medusajs/framework/utils"
- doSomethingStep(today)
+// recommended to define type in another file
+type ModuleOptions = {
+ apiKey?: string
+}
+
+export default async function validationLoader({
+ options,
+}: LoaderOptions) {
+ if (!options.apiKey) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Hello Module requires an apiKey option."
+ )
}
-)
+}
```
-In this workflow, `today` is only evaluated when the workflow is executed.
+Then, export the loader in the module's definition file, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md):
-***
+```ts title="src/modules/hello/index.ts"
+// other imports...
+import validationLoader from "./loaders/validate"
-## Caveats
+export default Module("hello", {
+ // ...
+ loaders: [validationLoader],
+})
+```
-### Transform Evaluation
+Now, when the Medusa application starts, the loader will run, validating the module's options and throwing an error if the `apiKey` option is missing.
-`transform`'s value is only evaluated if you pass its output to a step or in the workflow response.
-For example, if you have the following workflow:
+# Service Factory
-```ts
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input) {
- const str = transform(
- { input },
- (data) => `${data.input.str1}${data.input.str2}`
- )
+In this chapter, you’ll learn about what the service factory is and how to use it.
- return new WorkflowResponse("done")
- }
-)
-```
+## What is the Service Factory?
-Since `str`'s value isn't used as a step's input or passed to `WorkflowResponse`, its value is never evaluated.
+Medusa provides a service factory that your module’s main service can extend.
-### Data Validation
+The service factory generates data management methods for your data models in the database, so you don't have to implement these methods manually.
-`transform` should only be used to perform variable or data manipulation.
+Your service provides data-management functionalities of your data models.
-If you want to perform some validation on the data, use a step or [when-then](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md) instead.
+***
-For example:
+## How to Extend the Service Factory?
-```ts
-// DON'T
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input) {
- const str = transform(
- { input },
- (data) => {
- if (!input.str1) {
- throw new Error("Not allowed!")
- }
- }
- )
- }
-)
+Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods.
-// DO
-const validateHasStr1Step = createStep(
- "validate-has-str1",
- ({ input }) => {
- if (!input.str1) {
- throw new Error("Not allowed!")
- }
- }
-)
+For example, create the file `src/modules/hello/service.ts` with the following content:
-const myWorkflow = createWorkflow(
- "hello-world",
- function (input) {
- validateHasStr1({
- input,
- })
+```ts title="src/modules/hello/service.ts" highlights={highlights}
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
- // workflow continues its execution only if
- // the step doesn't throw the error.
- }
-)
-```
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ // TODO implement custom methods
+}
+export default HelloModuleService
+```
-# Workflow Timeout
+### MedusaService Parameters
-In this chapter, you’ll learn how to set a timeout for workflows and steps.
+The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
-## What is a Workflow Timeout?
+In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`.
-By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
+### Generated Methods
-You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown.
+The service factory generates methods to manage the records of each of the data models provided in the first parameter in the database.
-### Timeout Doesn't Stop Step Execution
+The method's names are the operation's name, suffixed by the data model's key in the object parameter passed to `MedusaService`.
-Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result.
+For example, the following methods are generated for the service above:
-***
+Find a complete reference of each of the methods in [this documentation](https://docs.medusajs.com/resources/service-factory-reference/index.html.md)
-## Configure Workflow Timeout
+### listMyCustoms
-The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
+### listMyCustoms
-In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
+This method retrieves an array of records based on filters and pagination configurations.
For example:
-```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- "step-1",
- async () => {
- // ...
- }
-)
-
-const myWorkflow = createWorkflow({
- name: "hello-world",
- timeout: 2, // 2 seconds
-}, function () {
- const str1 = step1()
+```ts
+const myCustoms = await helloModuleService
+ .listMyCustoms()
- return new WorkflowResponse({
- message: str1,
+// with filters
+const myCustoms = await helloModuleService
+ .listMyCustoms({
+ id: ["123"]
})
-})
-
-export default myWorkflow
-
```
-This workflow's executions fail if they run longer than two seconds.
-
-A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`.
-
-***
-
-## Configure Step Timeout
-
-Alternatively, you can configure the timeout for a step rather than the entire workflow.
+### listAndCount
-As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output.
+### retrieveMyCustom
-The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
+This method retrieves a record by its ID.
For example:
-```tsx
-const step1 = createStep(
- {
- name: "step-1",
- timeout: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
-```
-
-This step's executions fail if they run longer than two seconds.
-
-A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`.
-
-
-# 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 `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`:
-
-```ts title="src/modules/hello/__tests__/service.spec.ts"
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import { HELLO_MODULE } from ".."
-import HelloModuleService from "../service"
-import MyCustom from "../models/my-custom"
-
-moduleIntegrationTestRunner({
- moduleName: HELLO_MODULE,
- moduleModels: [MyCustom],
- resolve: "./src/modules/hello",
- testSuite: ({ service }) => {
- // TODO write tests
- },
-})
-
-jest.setTimeout(60 * 1000)
+```ts
+const myCustom = await helloModuleService
+ .retrieveMyCustom("123")
```
-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 model.
-- `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.
+### retrieveMyCustom
-The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property.
+### updateMyCustoms
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+This method updates and retrieves records of the data model.
-***
+For example:
-## Run Tests
+```ts
+const myCustom = await helloModuleService
+ .updateMyCustoms({
+ id: "123",
+ name: "test"
+ })
-Run the following command to run your module integration tests:
+// update multiple
+const myCustoms = await helloModuleService
+ .updateMyCustoms([
+ {
+ id: "123",
+ name: "test"
+ },
+ {
+ id: "321",
+ name: "test 2"
+ },
+ ])
-```bash npm2yarn
-npm run test:integration:modules
+// use filters
+const myCustoms = await helloModuleService
+ .updateMyCustoms([
+ {
+ selector: {
+ id: ["123", "321"]
+ },
+ data: {
+ name: "test"
+ }
+ },
+ ])
```
-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.
-
-***
+### createMyCustoms
-## Pass Module Options
+### softDeleteMyCustoms
-If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
+This method soft-deletes records using an array of IDs or an object of filters.
For example:
```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import HelloModuleService from "../service"
+await helloModuleService.softDeleteMyCustoms("123")
-moduleIntegrationTestRunner({
- moduleOptions: {
- apiKey: "123",
- },
- // ...
+// soft-delete multiple
+await helloModuleService.softDeleteMyCustoms([
+ "123", "321"
+])
+
+// use filters
+await helloModuleService.softDeleteMyCustoms({
+ id: ["123", "321"]
})
```
-***
-
-## 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 HelloModuleService from "../service"
-import { model } from "@medusajs/framework/utils"
-
-const DummyModel = model.define("dummy_model", {
- id: model.id().primaryKey(),
-})
+### updateMyCustoms
-moduleIntegrationTestRunner({
- moduleModels: [DummyModel],
- // ...
-})
+### deleteMyCustoms
-jest.setTimeout(60 * 1000)
-```
+### softDeleteMyCustoms
-***
+### restoreMyCustoms
-### Other Options and Inputs
+### Using a Constructor
-Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+If you implement the `constructor` of your service, make sure to call `super` passing it `...arguments`.
-***
+For example:
-## Database Used in Tests
+```ts highlights={[["8"]]}
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
-The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ constructor() {
+ super(...arguments)
+ }
+}
-To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
+export default HelloModuleService
+```
# Write Integration Tests
@@ -13783,34 +13805,19 @@ To manage that database, such as changing its name or perform operations on it i
The next chapters provide examples of writing integration tests for API routes and workflows.
-# Example: Integration Tests for a Module
+# Write Tests for Modules
-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.
+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)
-## Write Integration Test for Module
-
-Consider a `hello` module with a `HelloModuleService` that has a `getMessage` method:
-
-```ts title="src/modules/hello/service.ts"
-import { MedusaService } from "@medusajs/framework/utils"
-import MyCustom from "./models/my-custom"
-
-class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- getMessage(): string {
- return "Hello, World!"
- }
-}
+## moduleIntegrationTestRunner Utility
-export default HelloModuleService
-```
+`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
-To create an integration test for the method, create the file `src/modules/hello/__tests__/service.spec.ts` with the following content:
+For example, assuming you have a `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`:
```ts title="src/modules/hello/__tests__/service.spec.ts"
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
@@ -13823,24 +13830,29 @@ moduleIntegrationTestRunner({
moduleModels: [MyCustom],
resolve: "./src/modules/hello",
testSuite: ({ service }) => {
- describe("HelloModuleService", () => {
- it("says hello world", () => {
- const message = service.getMessage()
-
- expect(message).toEqual("Hello, World!")
- })
- })
+ // TODO write tests
},
})
jest.setTimeout(60 * 1000)
```
-You use the `moduleIntegrationTestRunner` function to add tests for the `hello` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string.
+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 model.
+- `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 Test
+## Run Tests
Run the following command to run your module integration tests:
@@ -13852,6 +13864,65 @@ If you don't have a `test:integration:modules` script in `package.json`, refer t
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 HelloModuleService 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 HelloModuleService 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 [this reference in the Development Resources documentation](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 [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md).
+
# Example: Write Integration Tests for API Routes
@@ -14550,155 +14621,95 @@ 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`.
-# Commerce Modules
-
-In this section of the documentation, you'll find guides and references related to Medusa's commerce modules.
+# Example: Integration Tests for a Module
-A commerce module provides features for a commerce domain within its service. The Medusa application exposes these features in its API routes to clients.
+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.
-A commerce module also defines data models, representing tables in the database. Medusa's framework and tools allow you to extend these data models to add custom fields.
+### Prerequisites
-## Commerce Modules List
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-***
+## Write Integration Test for Module
-## How to Use Modules
+Consider a `hello` module with a `HelloModuleService` that has a `getMessage` method:
-The Commerce Modules can be used in many use cases, including:
+```ts title="src/modules/hello/service.ts"
+import { MedusaService } from "@medusajs/framework/utils"
+import MyCustom from "./models/my-custom"
-- Medusa Application: The Medusa application uses the Commerce Modules to expose commerce features through the REST APIs.
-- Serverless Application: Use the Commerce Modules in a serverless application, such as a Next.js application, without having to manage a fully-fledged ecommerce system. You can use it by installing it in your Node.js project as an NPM package.
-- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
+class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ getMessage(): string {
+ return "Hello, World!"
+ }
+}
+export default HelloModuleService
+```
-# Auth Module
+To create an integration test for the method, create the file `src/modules/hello/__tests__/service.spec.ts` with the following content:
-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.
+```ts title="src/modules/hello/__tests__/service.spec.ts"
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import { HELLO_MODULE } from ".."
+import HelloModuleService from "../service"
+import MyCustom from "../models/my-custom"
-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.
+moduleIntegrationTestRunner({
+ moduleName: HELLO_MODULE,
+ moduleModels: [MyCustom],
+ resolve: "./src/modules/hello",
+ testSuite: ({ service }) => {
+ describe("HelloModuleService", () => {
+ it("says hello world", () => {
+ const message = service.getMessage()
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+ expect(message).toEqual("Hello, World!")
+ })
+ })
+ },
+})
-## Auth Features
+jest.setTimeout(60 * 1000)
+```
-- [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.
+You use the `moduleIntegrationTestRunner` function to add tests for the `hello` module. You have one test that passes if the `getMessage` method returns the `"Hello, World!"` string.
***
-## 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])
- }
-)
+## Run Test
-export const authenticateUserWorkflow = createWorkflow(
- "authenticate-user",
- (input: Input) => {
- const { authIdentity } = authenticateUserStep(input)
+Run the following command to run your module integration tests:
- return new WorkflowResponse({
- authIdentity,
- })
- }
-)
+```bash npm2yarn
+npm run test:integration:modules
```
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+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).
-```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"
+This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await authenticateUserWorkflow(req.scope)
- .run({
- req,
- })
- res.send(result)
-}
-```
+# Commerce Modules
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+In this section of the documentation, you'll find guides and references related to Medusa's commerce modules.
-***
+A commerce module provides features for a commerce domain within its service. The Medusa application exposes these features in its API routes to clients.
-## Configure Auth Module
+A commerce module also defines data models, representing tables in the database. Medusa's framework and tools allow you to extend these data models to add custom fields.
-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.
+## Commerce Modules List
***
-## Providers
+## How to Use Modules
-Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
+The Commerce Modules can be used in many use cases, including:
-***
+- Medusa Application: The Medusa application uses the Commerce Modules to expose commerce features through the REST APIs.
+- Serverless Application: Use the Commerce Modules in a serverless application, such as a Next.js application, without having to manage a fully-fledged ecommerce system. You can use it by installing it in your Node.js project as an NPM package.
+- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
# API Key Module
@@ -14841,24 +14852,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Cart Module
+# Currency 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 Currency 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/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).
-## Cart Features
+## Currency 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.
+- [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 Cart 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.
@@ -14866,54 +14877,46 @@ 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/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 createCartStep = createStep(
- "create-cart",
+const retrieveCurrencyStep = createStep(
+ "retrieve-currency",
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,
- },
- ],
- })
+ const currencyModuleService = container.resolve(Modules.CURRENCY)
- return new StepResponse({ cart }, cart.id)
- },
- async (cartId, { container }) => {
- if (!cartId) {
- return
- }
- const cartModuleService = container.resolve(Modules.CART)
+ const currency = await currencyModuleService
+ .retrieveCurrency("usd")
- await cartModuleService.deleteCarts([cartId])
+ return new StepResponse({ currency })
}
)
-export const createCartWorkflow = createWorkflow(
- "create-cart",
- () => {
- const { cart } = createCartStep()
+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({
- cart,
+ formattedPrice,
})
}
)
@@ -14923,19 +14926,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 { createCartWorkflow } from "../../workflows/create-cart"
+import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createCartWorkflow(req.scope)
- .run()
+ const { result } = await retrievePriceWithCurrency(req.scope)
+ .run({
+ price: 10,
+ })
res.send(result)
}
@@ -14943,19 +14948,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 { createCartWorkflow } from "../workflows/create-cart"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createCartWorkflow(container)
- .run()
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
console.log(result)
}
@@ -14967,15 +14974,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 { createCartWorkflow } from "../workflows/create-cart"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createCartWorkflow(container)
- .run()
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
console.log(result)
}
@@ -14991,24 +15000,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# 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.
+# Auth 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.
+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 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.
+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).
-## Customer Features
+## Auth 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).
+- [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 Customer Module
+## 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.
@@ -15016,45 +15025,67 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-customer.ts" highlights={highlights}
+```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
+import { Modules, MedusaError } from "@medusajs/framework/utils"
+import { MedusaRequest } from "@medusajs/framework/http"
+import { AuthenticationInput } from "@medusajs/framework/types"
-const createCustomerStep = createStep(
- "create-customer",
- async ({}, { container }) => {
- const customerModuleService = container.resolve(Modules.CUSTOMER)
+type Input = {
+ req: MedusaRequest
+}
- const customer = await customerModuleService.createCustomers({
- first_name: "Peter",
- last_name: "Hayes",
- email: "peter.hayes@example.com",
- })
+const authenticateUserStep = createStep(
+ "authenticate-user",
+ async ({ req }: Input, { container }) => {
+ const authModuleService = container.resolve(Modules.AUTH)
- return new StepResponse({ customer }, customer.id)
+ 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 (customerId, { container }) => {
- if (!customerId) {
+ async (authIdentityId, { container }) => {
+ if (!authIdentityId) {
return
}
- const customerModuleService = container.resolve(Modules.CUSTOMER)
+
+ const authModuleService = container.resolve(Modules.AUTH)
- await customerModuleService.deleteCustomers([customerId])
+ await authModuleService.deleteAuthIdentities([authIdentityId])
}
)
-export const createCustomerWorkflow = createWorkflow(
- "create-customer",
- () => {
- const { customer } = createCustomerStep()
+export const authenticateUserWorkflow = createWorkflow(
+ "authenticate-user",
+ (input: Input) => {
+ const { authIdentity } = authenticateUserStep(input)
return new WorkflowResponse({
- customer,
+ authIdentity,
})
}
)
@@ -15062,97 +15093,61 @@ export const createCustomerWorkflow = createWorkflow(
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"
+```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createCustomerWorkflow } from "../../workflows/create-customer"
+import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createCustomerWorkflow(req.scope)
- .run()
+ const { result } = await authenticateUserWorkflow(req.scope)
+ .run({
+ req,
+ })
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",
-}
-```
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-### Scheduled Job
+***
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createCustomerWorkflow } from "../workflows/create-customer"
+## Configure Auth Module
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createCustomerWorkflow(container)
- .run()
+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.
- console.log(result)
-}
+***
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
+## Providers
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+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.
+# Cart Module
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/inventory/index.html.md) to learn how to manage inventory and related features using the dashboard.
+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 inventory related features available out-of-the-box through the Inventory 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 Inventory Module.
+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).
-## Inventory Features
+## Cart Features
-- [Inventory Items Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md): Store and manage inventory of any stock-kept item, such as product variants.
-- [Inventory Across Locations](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventorylevel/index.html.md): Manage inventory levels across different locations, such as warehouses.
-- [Reservation Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#reservationitem/index.html.md): Reserve quantities of inventory items at specific locations for orders or other purposes.
-- [Check Inventory Availability](https://docs.medusajs.com/references/inventory-next/confirmInventory/index.html.md): Check whether an inventory item has the necessary quantity for purchase.
-- [Inventory Kits](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.
+- [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 Inventory Module
+## 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.
@@ -15160,7 +15155,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-inventory-item.ts" highlights={highlights}
+```ts title="src/workflows/create-cart.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -15169,36 +15164,45 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createInventoryItemStep = createStep(
- "create-inventory-item",
+const createCartStep = createStep(
+ "create-cart",
async ({}, { container }) => {
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
+ const cartModuleService = container.resolve(Modules.CART)
- const inventoryItem = await inventoryModuleService.createInventoryItems({
- sku: "SHIRT",
- title: "Green Medusa Shirt",
- requires_shipping: true,
+ 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({ inventoryItem }, inventoryItem.id)
+ return new StepResponse({ cart }, cart.id)
},
- async (inventoryItemId, { container }) => {
- if (!inventoryItemId) {
+ async (cartId, { container }) => {
+ if (!cartId) {
return
}
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
+ const cartModuleService = container.resolve(Modules.CART)
- await inventoryModuleService.deleteInventoryItems([inventoryItemId])
+ await cartModuleService.deleteCarts([cartId])
}
)
-export const createInventoryItemWorkflow = createWorkflow(
- "create-inventory-item-workflow",
+export const createCartWorkflow = createWorkflow(
+ "create-cart",
() => {
- const { inventoryItem } = createInventoryItemStep()
+ const { cart } = createCartStep()
return new WorkflowResponse({
- inventoryItem,
+ cart,
})
}
)
@@ -15213,13 +15217,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
+import { createCartWorkflow } from "../../workflows/create-cart"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createInventoryItemWorkflow(req.scope)
+ const { result } = await createCartWorkflow(req.scope)
.run()
res.send(result)
@@ -15233,13 +15237,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
+import { createCartWorkflow } from "../workflows/create-cart"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createInventoryItemWorkflow(container)
+ const { result } = await createCartWorkflow(container)
.run()
console.log(result)
@@ -15254,12 +15258,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
+import { createCartWorkflow } from "../workflows/create-cart"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createInventoryItemWorkflow(container)
+ const { result } = await createCartWorkflow(container)
.run()
console.log(result)
@@ -15276,24 +15280,29 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Currency Module
+# Fulfillment 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.
+In this section of the documentation, you will find resources to learn more about the Fulfillment 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.
+Refer to the Medusa Admin User Guide to learn how to use the dashboard to:
-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.
+- [Manage order fulfillments](https://docs.medusajs.com/user-guide/orders/fulfillments/index.html.md).
+- [Manage shipping options and profiles](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/index.html.md).
+
+Medusa has fulfillment related features available out-of-the-box through the Fulfillment 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 Fulfillment 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
+## Fulfillment 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.
+- [Fulfillment Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/item-fulfillment/index.html.md): Create fulfillments and keep track of their status, items, and more.
+- [Integrate Third-Party Fulfillment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/fulfillment-provider/index.html.md): Create third-party fulfillment providers to provide customers with shipping options and fulfill their orders.
+- [Restrict By Location and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/shipping-option/index.html.md): Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options.
+- [Support Different Fulfillment Forms](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts/index.html.md): Support various fulfillment forms, such as shipping or pick up.
***
-## How to Use the Currency Module
+## How to Use the Fulfillment 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.
@@ -15301,46 +15310,59 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
+```ts title="src/workflows/create-fulfillment.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",
+const createFulfillmentStep = createStep(
+ "create-fulfillment",
async ({}, { container }) => {
- const currencyModuleService = container.resolve(Modules.CURRENCY)
+ const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
- const currency = await currencyModuleService
- .retrieveCurrency("usd")
+ const fulfillment = await fulfillmentModuleService.createFulfillment({
+ location_id: "loc_123",
+ provider_id: "webshipper",
+ delivery_address: {
+ country_code: "us",
+ city: "Strongsville",
+ address_1: "18290 Royalton Rd",
+ },
+ items: [
+ {
+ title: "Shirt",
+ sku: "SHIRT",
+ quantity: 1,
+ barcode: "123456",
+ },
+ ],
+ labels: [],
+ order: {},
+ })
- return new StepResponse({ currency })
+ return new StepResponse({ fulfillment }, fulfillment.id)
+ },
+ async (fulfillmentId, { container }) => {
+ if (!fulfillmentId) {
+ return
+ }
+ const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
+
+ await fulfillmentModuleService.deleteFulfillment(fulfillmentId)
}
)
-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}`
- })
+export const createFulfillmentWorkflow = createWorkflow(
+ "create-fulfillment",
+ () => {
+ const { fulfillment } = createFulfillmentStep()
return new WorkflowResponse({
- formattedPrice,
+ fulfillment,
})
}
)
@@ -15350,21 +15372,19 @@ 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"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+```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 { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
+import { createFulfillmentWorkflow } from "../../workflows/create-fuilfillment"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await retrievePriceWithCurrency(req.scope)
- .run({
- price: 10,
- })
+ const { result } = await createFulfillmentWorkflow(req.scope)
+ .run()
res.send(result)
}
@@ -15372,21 +15392,19 @@ export async function GET(
### Subscriber
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+```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 { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
+ const { result } = await createFulfillmentWorkflow(container)
+ .run()
console.log(result)
}
@@ -15398,17 +15416,15 @@ export const config: SubscriberConfig = {
### Scheduled Job
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
+ const { result } = await createFulfillmentWorkflow(container)
+ .run()
console.log(result)
}
@@ -15423,30 +15439,34 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+## Configure Fulfillment Module
-# Fulfillment Module
+The Fulfillment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) for details on the module's options.
-In this section of the documentation, you will find resources to learn more about the Fulfillment Module and how to use it in your application.
+***
-Refer to the Medusa Admin User Guide to learn how to use the dashboard to:
-- [Manage order fulfillments](https://docs.medusajs.com/user-guide/orders/fulfillments/index.html.md).
-- [Manage shipping options and profiles](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/index.html.md).
+# Inventory Module
-Medusa has fulfillment related features available out-of-the-box through the Fulfillment 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 Fulfillment 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.
+
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/inventory/index.html.md) to learn how to manage inventory and related features using the dashboard.
+
+Medusa has inventory related features available out-of-the-box through the Inventory 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 Inventory Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Fulfillment Features
+## Inventory Features
-- [Fulfillment Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/item-fulfillment/index.html.md): Create fulfillments and keep track of their status, items, and more.
-- [Integrate Third-Party Fulfillment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/fulfillment-provider/index.html.md): Create third-party fulfillment providers to provide customers with shipping options and fulfill their orders.
-- [Restrict By Location and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/shipping-option/index.html.md): Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options.
-- [Support Different Fulfillment Forms](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts/index.html.md): Support various fulfillment forms, such as shipping or pick up.
+- [Inventory Items Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md): Store and manage inventory of any stock-kept item, such as product variants.
+- [Inventory Across Locations](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventorylevel/index.html.md): Manage inventory levels across different locations, such as warehouses.
+- [Reservation Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#reservationitem/index.html.md): Reserve quantities of inventory items at specific locations for orders or other purposes.
+- [Check Inventory Availability](https://docs.medusajs.com/references/inventory-next/confirmInventory/index.html.md): Check whether an inventory item has the necessary quantity for purchase.
+- [Inventory Kits](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 Fulfillment Module
+## How to Use the Inventory 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.
@@ -15454,7 +15474,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-fulfillment.ts" highlights={highlights}
+```ts title="src/workflows/create-inventory-item.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -15463,50 +15483,36 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createFulfillmentStep = createStep(
- "create-fulfillment",
+const createInventoryItemStep = createStep(
+ "create-inventory-item",
async ({}, { container }) => {
- const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
- const fulfillment = await fulfillmentModuleService.createFulfillment({
- location_id: "loc_123",
- provider_id: "webshipper",
- delivery_address: {
- country_code: "us",
- city: "Strongsville",
- address_1: "18290 Royalton Rd",
- },
- items: [
- {
- title: "Shirt",
- sku: "SHIRT",
- quantity: 1,
- barcode: "123456",
- },
- ],
- labels: [],
- order: {},
+ const inventoryItem = await inventoryModuleService.createInventoryItems({
+ sku: "SHIRT",
+ title: "Green Medusa Shirt",
+ requires_shipping: true,
})
- return new StepResponse({ fulfillment }, fulfillment.id)
+ return new StepResponse({ inventoryItem }, inventoryItem.id)
},
- async (fulfillmentId, { container }) => {
- if (!fulfillmentId) {
+ async (inventoryItemId, { container }) => {
+ if (!inventoryItemId) {
return
}
- const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
- await fulfillmentModuleService.deleteFulfillment(fulfillmentId)
+ await inventoryModuleService.deleteInventoryItems([inventoryItemId])
}
)
-export const createFulfillmentWorkflow = createWorkflow(
- "create-fulfillment",
+export const createInventoryItemWorkflow = createWorkflow(
+ "create-inventory-item-workflow",
() => {
- const { fulfillment } = createFulfillmentStep()
+ const { inventoryItem } = createInventoryItemStep()
return new WorkflowResponse({
- fulfillment,
+ inventoryItem,
})
}
)
@@ -15521,13 +15527,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createFulfillmentWorkflow } from "../../workflows/create-fuilfillment"
+import { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createFulfillmentWorkflow(req.scope)
+ const { result } = await createInventoryItemWorkflow(req.scope)
.run()
res.send(result)
@@ -15541,13 +15547,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
+import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createFulfillmentWorkflow(container)
+ const { result } = await createInventoryItemWorkflow(container)
.run()
console.log(result)
@@ -15562,12 +15568,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
+import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createFulfillmentWorkflow(container)
+ const { result } = await createInventoryItemWorkflow(container)
.run()
console.log(result)
@@ -15583,12 +15589,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-## Configure Fulfillment Module
-
-The Fulfillment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) for details on the module's options.
-
-***
-
# Order Module
@@ -15900,160 +15900,6 @@ 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).
-
-***
-
-
# Payment Module
In this section of the documentation, you will find resources to learn more about the Payment Module and how to use it in your application.
@@ -16209,27 +16055,25 @@ Medusa provides the following payment providers out-of-the-box. You can use them
***
-# Region Module
+# Product Module
-In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application.
+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/settings/regions/index.html.md) to learn how to manage regions using the dashboard.
+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 region related features available out-of-the-box through the Region 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 Region Module.
+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).
-***
-
-## Region Features
+## Product Features
-- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings.
-- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions.
-- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings.
+- [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 Region Module's Service
+## 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.
@@ -16237,7 +16081,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-region.ts" highlights={highlights}
+```ts title="src/workflows/create-product.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -16246,35 +16090,48 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createRegionStep = createStep(
- "create-region",
+const createProductStep = createStep(
+ "create-product",
async ({}, { container }) => {
- const regionModuleService = container.resolve(Modules.REGION)
+ const productService = container.resolve(Modules.PRODUCT)
- const region = await regionModuleService.createRegions({
- name: "Europe",
- currency_code: "eur",
+ const product = await productService.createProducts({
+ title: "Medusa Shirt",
+ options: [
+ {
+ title: "Color",
+ values: ["Black", "White"],
+ },
+ ],
+ variants: [
+ {
+ title: "Black Shirt",
+ options: {
+ Color: "Black",
+ },
+ },
+ ],
})
- return new StepResponse({ region }, region.id)
+ return new StepResponse({ product }, product.id)
},
- async (regionId, { container }) => {
- if (!regionId) {
+ async (productId, { container }) => {
+ if (!productId) {
return
}
- const regionModuleService = container.resolve(Modules.REGION)
+ const productService = container.resolve(Modules.PRODUCT)
- await regionModuleService.deleteRegions([regionId])
+ await productService.deleteProducts([productId])
}
)
-export const createRegionWorkflow = createWorkflow(
- "create-region",
+export const createProductWorkflow = createWorkflow(
+ "create-product",
() => {
- const { region } = createRegionStep()
+ const { product } = createProductStep()
return new WorkflowResponse({
- region,
+ product,
})
}
)
@@ -16289,13 +16146,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createRegionWorkflow } from "../../workflows/create-region"
+import { createProductWorkflow } from "../../workflows/create-product"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createRegionWorkflow(req.scope)
+ const { result } = await createProductWorkflow(req.scope)
.run()
res.send(result)
@@ -16309,13 +16166,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createRegionWorkflow } from "../workflows/create-region"
+import { createProductWorkflow } from "../workflows/create-product"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createRegionWorkflow(container)
+ const { result } = await createProductWorkflow(container)
.run()
console.log(result)
@@ -16330,12 +16187,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createRegionWorkflow } from "../workflows/create-region"
+import { createProductWorkflow } from "../workflows/create-product"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createRegionWorkflow(container)
+ const { result } = await createProductWorkflow(container)
.run()
console.log(result)
@@ -16352,38 +16209,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Sales Channel Module
+# Customer 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.
+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/settings/sales-channels/index.html.md) to learn how to manage sales channels using the dashboard.
+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 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.
+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).
-## 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
+## Customer 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.
+- [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 Sales Channel Module's Service
+## 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.
@@ -16391,7 +16234,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-sales-channel.ts" highlights={highlights}
+```ts title="src/workflows/create-customer.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -16400,41 +16243,36 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createSalesChannelStep = createStep(
- "create-sales-channel",
+const createCustomerStep = createStep(
+ "create-customer",
async ({}, { container }) => {
- const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
+ const customerModuleService = container.resolve(Modules.CUSTOMER)
- const salesChannels = await salesChannelModuleService.createSalesChannels([
- {
- name: "B2B",
- },
- {
- name: "Mobile App",
- },
- ])
+ const customer = await customerModuleService.createCustomers({
+ first_name: "Peter",
+ last_name: "Hayes",
+ email: "peter.hayes@example.com",
+ })
- return new StepResponse({ salesChannels }, salesChannels.map((sc) => sc.id))
+ return new StepResponse({ customer }, customer.id)
},
- async (salesChannelIds, { container }) => {
- if (!salesChannelIds) {
+ async (customerId, { container }) => {
+ if (!customerId) {
return
}
- const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
+ const customerModuleService = container.resolve(Modules.CUSTOMER)
- await salesChannelModuleService.deleteSalesChannels(
- salesChannelIds
- )
+ await customerModuleService.deleteCustomers([customerId])
}
)
-export const createSalesChannelWorkflow = createWorkflow(
- "create-sales-channel",
+export const createCustomerWorkflow = createWorkflow(
+ "create-customer",
() => {
- const { salesChannels } = createSalesChannelStep()
+ const { customer } = createCustomerStep()
return new WorkflowResponse({
- salesChannels,
+ customer,
})
}
)
@@ -16449,13 +16287,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createSalesChannelWorkflow } from "../../workflows/create-sales-channel"
+import { createCustomerWorkflow } from "../../workflows/create-customer"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createSalesChannelWorkflow(req.scope)
+ const { result } = await createCustomerWorkflow(req.scope)
.run()
res.send(result)
@@ -16469,13 +16307,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
+import { createCustomerWorkflow } from "../workflows/create-customer"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createSalesChannelWorkflow(container)
+ const { result } = await createCustomerWorkflow(container)
.run()
console.log(result)
@@ -16490,12 +16328,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
+import { createCustomerWorkflow } from "../workflows/create-customer"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createSalesChannelWorkflow(container)
+ const { result } = await createCustomerWorkflow(container)
.run()
console.log(result)
@@ -16512,24 +16350,27 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Stock Location Module
+# Region 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.
+In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application.
-Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/index.html.md) to learn how to manage stock locations using the dashboard.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/regions/index.html.md) to learn how to manage regions using the dashboard.
-Medusa has stock location related features available out-of-the-box through the Stock Location 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 Stock Location Module.
+Medusa has region related features available out-of-the-box through the Region 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 Region Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Stock Location Features
+***
-- [Stock Location Management](https://docs.medusajs.com/references/stock-location-next/models/index.html.md): Store and manage stock locations. Medusa links stock locations with data models of other modules that require a location, such as the [Inventory Module's InventoryLevel](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/links-to-other-modules/index.html.md).
-- [Address Management](https://docs.medusajs.com/references/stock-location-next/models/StockLocationAddress/index.html.md): Manage the address of each stock location.
+## Region Features
+
+- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings.
+- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions.
+- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings.
***
-## How to Use Stock Location Module's Service
+## How to Use Region 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.
@@ -16537,7 +16378,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-stock-location.ts" highlights={highlights}
+```ts title="src/workflows/create-region.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -16546,33 +16387,36 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createStockLocationStep = createStep(
- "create-stock-location",
+const createRegionStep = createStep(
+ "create-region",
async ({}, { container }) => {
- const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
+ const regionModuleService = container.resolve(Modules.REGION)
- const stockLocation = await stockLocationModuleService.createStockLocations({
- name: "Warehouse 1",
+ const region = await regionModuleService.createRegions({
+ name: "Europe",
+ currency_code: "eur",
})
- return new StepResponse({ stockLocation }, stockLocation.id)
+ return new StepResponse({ region }, region.id)
},
- async (stockLocationId, { container }) => {
- if (!stockLocationId) {
+ async (regionId, { container }) => {
+ if (!regionId) {
return
}
- const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
+ const regionModuleService = container.resolve(Modules.REGION)
- await stockLocationModuleService.deleteStockLocations([stockLocationId])
+ await regionModuleService.deleteRegions([regionId])
}
)
-export const createStockLocationWorkflow = createWorkflow(
- "create-stock-location",
+export const createRegionWorkflow = createWorkflow(
+ "create-region",
() => {
- const { stockLocation } = createStockLocationStep()
+ const { region } = createRegionStep()
- return new WorkflowResponse({ stockLocation })
+ return new WorkflowResponse({
+ region,
+ })
}
)
```
@@ -16586,13 +16430,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createStockLocationWorkflow } from "../../workflows/create-stock-location"
+import { createRegionWorkflow } from "../../workflows/create-region"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createStockLocationWorkflow(req.scope)
+ const { result } = await createRegionWorkflow(req.scope)
.run()
res.send(result)
@@ -16606,13 +16450,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createStockLocationWorkflow } from "../workflows/create-stock-location"
+import { createRegionWorkflow } from "../workflows/create-region"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createStockLocationWorkflow(container)
+ const { result } = await createRegionWorkflow(container)
.run()
console.log(result)
@@ -16627,12 +16471,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createStockLocationWorkflow } from "../workflows/create-stock-location"
+import { createRegionWorkflow } from "../workflows/create-region"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createStockLocationWorkflow(container)
+ const { result } = await createRegionWorkflow(container)
.run()
console.log(result)
@@ -16797,24 +16641,38 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Store Module
+# Sales Channel 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.
+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/store/index.html.md) to learn how to manage your store 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 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.
+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).
-## Store Features
+## What's a Sales Channel?
-- [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.
+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 Store 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.
@@ -16822,7 +16680,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-store.ts" highlights={highlights}
+```ts title="src/workflows/create-sales-channel.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -16831,37 +16689,42 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createStoreStep = createStep(
- "create-store",
+const createSalesChannelStep = createStep(
+ "create-sales-channel",
async ({}, { container }) => {
- const storeModuleService = container.resolve(Modules.STORE)
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
- const store = await storeModuleService.createStores({
- name: "My Store",
- supported_currencies: [{
- currency_code: "usd",
- is_default: true,
- }],
- })
+ const salesChannels = await salesChannelModuleService.createSalesChannels([
+ {
+ name: "B2B",
+ },
+ {
+ name: "Mobile App",
+ },
+ ])
- return new StepResponse({ store }, store.id)
+ return new StepResponse({ salesChannels }, salesChannels.map((sc) => sc.id))
},
- async (storeId, { container }) => {
- if(!storeId) {
+ async (salesChannelIds, { container }) => {
+ if (!salesChannelIds) {
return
}
- const storeModuleService = container.resolve(Modules.STORE)
-
- await storeModuleService.deleteStores([storeId])
+ const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
+
+ await salesChannelModuleService.deleteSalesChannels(
+ salesChannelIds
+ )
}
)
-export const createStoreWorkflow = createWorkflow(
- "create-store",
+export const createSalesChannelWorkflow = createWorkflow(
+ "create-sales-channel",
() => {
- const { store } = createStoreStep()
+ const { salesChannels } = createSalesChannelStep()
- return new WorkflowResponse({ store })
+ return new WorkflowResponse({
+ salesChannels,
+ })
}
)
```
@@ -16875,13 +16738,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createStoreWorkflow } from "../../workflows/create-store"
+import { createSalesChannelWorkflow } from "../../workflows/create-sales-channel"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createStoreWorkflow(req.scope)
+ const { result } = await createSalesChannelWorkflow(req.scope)
.run()
res.send(result)
@@ -16895,13 +16758,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createStoreWorkflow } from "../workflows/create-store"
+import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createStoreWorkflow(container)
+ const { result } = await createSalesChannelWorkflow(container)
.run()
console.log(result)
@@ -16916,12 +16779,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createStoreWorkflow } from "../workflows/create-store"
+import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createStoreWorkflow(container)
+ const { result } = await createSalesChannelWorkflow(container)
.run()
console.log(result)
@@ -16938,25 +16801,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Tax Module
+# Stock Location Module
-In this section of the documentation, you will find resources to learn more about the Tax Module and how to use it in your application.
+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.
-Refer to the [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.
+Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/locations-and-shipping/index.html.md) to learn how to manage stock locations using the dashboard.
-Medusa has tax related features available out-of-the-box through the Tax 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 Tax Module.
+Medusa has stock location related features available out-of-the-box through the Stock Location 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 Stock Location Module.
Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-## Tax Features
+## Stock Location Features
-- [Tax Settings Per Region](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md): Set different tax settings for each tax region.
-- [Tax Rates and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-rates-and-rules/index.html.md): Manage each region's default tax rates and override them with conditioned tax rates.
-- [Retrieve Tax Lines for carts and orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-calculation-with-provider/index.html.md): Calculate and retrieve the tax lines of a cart or order's line items and shipping methods with tax providers.
+- [Stock Location Management](https://docs.medusajs.com/references/stock-location-next/models/index.html.md): Store and manage stock locations. Medusa links stock locations with data models of other modules that require a location, such as the [Inventory Module's InventoryLevel](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/links-to-other-modules/index.html.md).
+- [Address Management](https://docs.medusajs.com/references/stock-location-next/models/StockLocationAddress/index.html.md): Manage the address of each stock location.
***
-## How to Use Tax Module's Service
+## How to Use Stock Location 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.
@@ -16964,7 +16826,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows
For example:
-```ts title="src/workflows/create-tax-region.ts" highlights={highlights}
+```ts title="src/workflows/create-stock-location.ts" highlights={highlights}
import {
createWorkflow,
WorkflowResponse,
@@ -16973,33 +16835,33 @@ import {
} from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
-const createTaxRegionStep = createStep(
- "create-tax-region",
+const createStockLocationStep = createStep(
+ "create-stock-location",
async ({}, { container }) => {
- const taxModuleService = container.resolve(Modules.TAX)
+ const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
- const taxRegion = await taxModuleService.createTaxRegions({
- country_code: "us",
+ const stockLocation = await stockLocationModuleService.createStockLocations({
+ name: "Warehouse 1",
})
- return new StepResponse({ taxRegion }, taxRegion.id)
+ return new StepResponse({ stockLocation }, stockLocation.id)
},
- async (taxRegionId, { container }) => {
- if (!taxRegionId) {
+ async (stockLocationId, { container }) => {
+ if (!stockLocationId) {
return
}
- const taxModuleService = container.resolve(Modules.TAX)
+ const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
- await taxModuleService.deleteTaxRegions([taxRegionId])
+ await stockLocationModuleService.deleteStockLocations([stockLocationId])
}
)
-export const createTaxRegionWorkflow = createWorkflow(
- "create-tax-region",
+export const createStockLocationWorkflow = createWorkflow(
+ "create-stock-location",
() => {
- const { taxRegion } = createTaxRegionStep()
+ const { stockLocation } = createStockLocationStep()
- return new WorkflowResponse({ taxRegion })
+ return new WorkflowResponse({ stockLocation })
}
)
```
@@ -17013,13 +16875,13 @@ import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
-import { createTaxRegionWorkflow } from "../../workflows/create-tax-region"
+import { createStockLocationWorkflow } from "../../workflows/create-stock-location"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await createTaxRegionWorkflow(req.scope)
+ const { result } = await createStockLocationWorkflow(req.scope)
.run()
res.send(result)
@@ -17033,13 +16895,13 @@ import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
-import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
+import { createStockLocationWorkflow } from "../workflows/create-stock-location"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await createTaxRegionWorkflow(container)
+ const { result } = await createStockLocationWorkflow(container)
.run()
console.log(result)
@@ -17054,12 +16916,12 @@ export const config: SubscriberConfig = {
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
import { MedusaContainer } from "@medusajs/framework/types"
-import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
+import { createStockLocationWorkflow } from "../workflows/create-stock-location"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await createTaxRegionWorkflow(container)
+ const { result } = await createStockLocationWorkflow(container)
.run()
console.log(result)
@@ -17075,12 +16937,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-## Configure Tax Module
-
-The Tax Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/module-options/index.html.md) for details on the module's options.
-
-***
-
# User Module
@@ -17229,362 +17085,576 @@ The User Module accepts options for further configurations. Refer to [this docum
***
-# Authentication Flows with the Auth Main Service
+# Tax Module
-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.
+In this section of the documentation, you will find resources to learn more about the Tax Module and how to use it in your application.
-## Authentication Methods
+Refer to the [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.
-### Register
+Medusa has tax related features available out-of-the-box through the Tax 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 Tax Module.
-The [register method of the Auth Module's main service](https://docs.medusajs.com/references/auth/register/index.html.md) creates an auth identity that can be authenticated later.
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-For example:
+## Tax Features
-```ts
-const data = await authModuleService.register(
- "emailpass",
- // passed to auth provider
- {
- // ...
- }
-)
-```
+- [Tax Settings Per Region](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-region/index.html.md): Set different tax settings for each tax region.
+- [Tax Rates and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-rates-and-rules/index.html.md): Manage each region's default tax rates and override them with conditioned tax rates.
+- [Retrieve Tax Lines for carts and orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/tax-calculation-with-provider/index.html.md): Calculate and retrieve the tax lines of a cart or order's line items and shipping methods with tax providers.
-This method calls the `register` method of the provider specified in the first parameter and returns its data.
+***
-### Authenticate
+## How to Use Tax Module's Service
-To authenticate a user, you use the [authenticate method of the Auth Module's main service](https://docs.medusajs.com/references/auth/authenticate/index.html.md). For example:
+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.
-```ts
-const data = await authModuleService.authenticate(
- "emailpass",
- // passed to auth provider
- {
- // ...
- }
-)
-```
+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.
-This method calls the `authenticate` method of the provider specified in the first parameter and returns its data.
+For example:
-***
+```ts title="src/workflows/create-tax-region.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
-## Auth Flow 1: Basic Authentication
+const createTaxRegionStep = createStep(
+ "create-tax-region",
+ async ({}, { container }) => {
+ const taxModuleService = container.resolve(Modules.TAX)
-The basic authentication flow requires first using the `register` method, then the `authenticate` method:
+ const taxRegion = await taxModuleService.createTaxRegions({
+ country_code: "us",
+ })
-```ts
-const { success, authIdentity, error } = await authModuleService.register(
- "emailpass",
- // passed to auth provider
- {
- // ...
+ return new StepResponse({ taxRegion }, taxRegion.id)
+ },
+ async (taxRegionId, { container }) => {
+ if (!taxRegionId) {
+ return
+ }
+ const taxModuleService = container.resolve(Modules.TAX)
+
+ await taxModuleService.deleteTaxRegions([taxRegionId])
}
)
-if (error) {
- // registration failed
- // TODO return an error
- return
-}
+export const createTaxRegionWorkflow = createWorkflow(
+ "create-tax-region",
+ () => {
+ const { taxRegion } = createTaxRegionStep()
-// later (can be another route for log-in)
-const { success, authIdentity, location } = await authModuleService.authenticate(
- "emailpass",
- // passed to auth provider
- {
- // ...
+ return new WorkflowResponse({ taxRegion })
}
)
-
-if (success && !location) {
- // user is authenticated
-}
```
-If `success` is true and `location` isn't set, the user is authenticated successfully, and their authentication details are available within the `authIdentity` object.
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-The next section explains the flow if `location` is set.
+### API Route
-Check out the [AuthIdentity](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) reference for the received properties in `authIdentity`.
+```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 { createTaxRegionWorkflow } from "../../workflows/create-tax-region"
-
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createTaxRegionWorkflow(req.scope)
+ .run()
-### Auth Identity with Same Identifier
+ res.send(result)
+}
+```
-If an auth identity, such as a `customer`, tries to register with an email of another auth identity, the `register` method returns an error. This can happen either if another customer is using the same email, or an admin user has the same email.
+### Subscriber
-There are two ways to handle this:
+```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 { createTaxRegionWorkflow } from "../workflows/create-tax-region"
-- Consider the customer authenticated if the `authenticate` method validates that the email and password are correct. This allows admin users, for example, to authenticate as customers.
-- Return an error message to the customer, informing them that the email is already in use.
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createTaxRegionWorkflow(container)
+ .run()
-***
+ console.log(result)
+}
-## Auth Flow 2: Third-Party Service Authentication
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
-The third-party service authentication method requires using the `authenticate` method first:
+### Scheduled Job
-```ts
-const { success, authIdentity, location } = await authModuleService.authenticate(
- "google",
- // passed to auth provider
- {
- // ...
- }
-)
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
-if (location) {
- // return the location for the front-end to redirect to
-}
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createTaxRegionWorkflow(container)
+ .run()
-if (!success) {
- // authentication failed
+ console.log(result)
}
-// authentication successful
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
```
-If the `authenticate` method returns a `location` property, the authentication process requires the user to perform an action with a third-party service. So, you return the `location` to the front-end or client to redirect to that URL.
-
-For example, when using the `google` provider, the `location` is the URL that the user is navigated to login.
-
-
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-### Overriding Callback URL
+***
-The Google and GitHub providers allow you to override their `callbackUrl` option during authentication. This is useful when you redirect the user after authentication to a URL based on its actor type. For example, you redirect admin users and customers to different pages.
+## Configure Tax Module
-```ts
-const { success, authIdentity, location } = await authModuleService.authenticate(
- "google",
- // passed to auth provider
- {
- // ...
- callback_url: "example.com",
- }
-)
-```
+The Tax Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/tax/module-options/index.html.md) for details on the module's options.
-### validateCallback
+***
-Providers handling this authentication flow must implement the `validateCallback` method. It implements the logic to validate the authentication with the third-party service.
-So, once the user performs the required action with the third-party service (for example, log-in with Google), the frontend must redirect to an API route that uses the [validateCallback method of the Auth Module's main service](https://docs.medusajs.com/references/auth/validateCallback/index.html.md).
+# Store Module
-The method calls the specified provider’s `validateCallback` method passing it the authentication details it received in the second parameter:
+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.
-```ts
-const { success, authIdentity } = await authModuleService.validateCallback(
- "google",
- // passed to auth provider
- {
- // request data, such as
- url,
- headers,
- query,
- body,
- protocol,
- }
-)
+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.
-if (success) {
- // authentication succeeded
-}
-```
+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.
-For providers like Google, the `query` object contains the query parameters from the original callback URL, such as the `code` and `state` parameters.
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-If the returned `success` property is `true`, the authentication with the third-party provider was successful.
+## 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.
***
-## Reset Password
+## How to Use Store Module's Service
-To update a user's password or other authentication details, use the `updateProvider` method of the Auth Module's main service. It calls the `update` method of the specified authentication provider.
+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
-const { success } = await authModuleService.updateProvider(
- "emailpass",
- // passed to the auth provider
- {
- entity_id: "user@example.com",
- password: "supersecret",
+```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])
}
)
-if (success) {
- // password reset successfully
+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)
}
```
-The method accepts as a first parameter the ID of the provider, and as a second parameter the data necessary to reset the password.
+### Subscriber
-In the example above, you use the `emailpass` provider, so you have to pass an object having an `email` and `password` properties.
+```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"
-If the returned `success` property is `true`, the password has reset successfully.
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createStoreWorkflow(container)
+ .run()
+ console.log(result)
+}
-# Auth Providers
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
-In this document, you’ll learn how the Auth Module handles authentication using providers.
+### Scheduled Job
-## What's an Auth Module Provider?
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createStoreWorkflow } from "../workflows/create-store"
-An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createStoreWorkflow(container)
+ .run()
-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.
+ console.log(result)
+}
-### Auth Providers List
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
-- [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)
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/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.
+# Links between API Key Module and Other Modules
-To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/references/medusa-config#http-authMethodsPerActor-1-3/index.html.md) in Medusa's configurations:
+This document showcases the module links defined between the API Key Module and other commerce modules.
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- http: {
- authMethodsPerActor: {
- user: ["google"],
- customer: ["emailpass"],
- },
- // ...
- },
- // ...
- },
-})
-```
+## Summary
-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.
+The API Key Module has the following links to other modules:
+
+- [`ApiKey` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module).
***
-## How to Create an Auth Module Provider
+## Sales Channel Module
-Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider.
+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.
+
-# Auth Identity and Actor Types
+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.
-In this document, you’ll learn about concepts related to identity and actors in the Auth Module.
+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).
-## What is an Auth Identity?
+### Retrieve with Query
-The [AuthIdentity data model](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) represents a user registered by an [authentication provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/index.html.md). When a user is registered using an authentication provider, the provider creates a record of `AuthIdentity`.
+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`:
-Then, when the user logs-in in the future with the same authentication provider, the associated auth identity is used to validate their credentials.
+### query.graph
-***
+```ts
+const { data: apiKeys } = await query.graph({
+ entity: "api_key",
+ fields: [
+ "sales_channels.*",
+ ],
+})
-## Actor Types
+// apiKeys.sales_channels
+```
-An actor type is a type of user that can be authenticated. The Auth Module doesn't store or manage any user-like models, such as for customers or users. Instead, the user types are created and managed by other modules. For example, a customer is managed by the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md).
+### useQueryGraphStep
-Then, when an auth identity is created for the actor type, the ID of the user is stored in the `app_metadata` property of the auth identity.
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-For example, an auth identity of a customer has the following `app_metadata` property:
+// ...
-```json
-{
- "app_metadata": {
- "customer_id": "cus_123"
- }
-}
-```
+const { data: apiKeys } = useQueryGraphStep({
+ entity: "api_key",
+ fields: [
+ "sales_channels.*",
+ ],
+})
-The ID of the user is stored in the key `{actor_type}_id` of the `app_metadata` property.
+// apiKeys.sales_channels
+```
-***
+### Manage with Link
-## Protect Routes by Actor Type
+To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-When you protect routes with the `authenticate` middleware, you specify in its first parameter the actor type that must be authenticated to access the specified API routes.
+### link.create
-For example:
+```ts
+import { Modules } from "@medusajs/framework/utils"
-```ts title="src/api/middlewares.ts" highlights={highlights}
-import {
- defineMiddlewares,
- authenticate,
-} from "@medusajs/framework/http"
+// ...
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/custom/admin*",
- middlewares: [
- authenticate("user", ["session", "bearer", "api-key"]),
- ],
- },
- ],
+await link.create({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
})
```
-By specifying `user` as the first parameter of `authenticate`, only authenticated users of actor type `user` (admin users) can access API routes starting with `/custom/admin`.
+### createRemoteLinkStep
-***
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-## Custom Actor Types
+// ...
-You can define custom actor types that allows a custom user, managed by your custom module, to authenticate into Medusa.
+createRemoteLinkStep({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
-For example, if you have a custom module with a `Manager` data model, you can authenticate managers with the `manager` actor type.
-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).
+# API Key Concepts
+In this document, you’ll learn about the different types of API keys, their expiration and verification.
-# How to Use Authentication Routes
+## API Key Types
-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.
+There are two types of API keys:
-These routes are added by Medusa's HTTP layer, not the Auth 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.
-## Types of Authentication Flows
+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).
-### 1. Basic Authentication Flow
+***
-This authentication flow doesn't require validation with third-party services.
+## API Key Expiration
-[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).
+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 steps are:
+The associated token is no longer usable or verifiable.
-
+***
-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).
+## Token Verification
-After registration, you only use the [Auth Route](#login-route) for subsequent authentication.
+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.
-To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
-### 2. Third-Party Service Authenticate Flow
+# Links between Currency Module and Other Modules
-This authentication flow authenticates the user with a third-party service, such as Google.
+This document showcases the module links defined between the Currency Module and other commerce modules.
-[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).
+## Summary
-It requires the following steps:
+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.
+
+- [`Currency` data model of Store Module \<> `Currency` data model of Currency Module](#store-module). (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 Currency Module's `Currency` data model and the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'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` data model in the Store 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
+```
+
+
+# Auth Identity and Actor Types
+
+In this document, you’ll learn about concepts related to identity and actors in the Auth Module.
+
+## What is an Auth Identity?
+
+The [AuthIdentity data model](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) represents a user registered by an [authentication provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/index.html.md). When a user is registered using an authentication provider, the provider creates a record of `AuthIdentity`.
+
+Then, when the user logs-in in the future with the same authentication provider, the associated auth identity is used to validate their credentials.
+
+***
+
+## Actor Types
+
+An actor type is a type of user that can be authenticated. The Auth Module doesn't store or manage any user-like models, such as for customers or users. Instead, the user types are created and managed by other modules. For example, a customer is managed by the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md).
+
+Then, when an auth identity is created for the actor type, the ID of the user is stored in the `app_metadata` property of the auth identity.
+
+For example, an auth identity of a customer has the following `app_metadata` property:
+
+```json
+{
+ "app_metadata": {
+ "customer_id": "cus_123"
+ }
+}
+```
+
+The ID of the user is stored in the key `{actor_type}_id` of the `app_metadata` property.
+
+***
+
+## Protect Routes by Actor Type
+
+When you protect routes with the `authenticate` middleware, you specify in its first parameter the actor type that must be authenticated to access the specified API routes.
+
+For example:
+
+```ts title="src/api/middlewares.ts" highlights={highlights}
+import {
+ defineMiddlewares,
+ authenticate,
+} from "@medusajs/framework/http"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom/admin*",
+ middlewares: [
+ authenticate("user", ["session", "bearer", "api-key"]),
+ ],
+ },
+ ],
+})
+```
+
+By specifying `user` as the first parameter of `authenticate`, only authenticated users of actor type `user` (admin users) can access API routes starting with `/custom/admin`.
+
+***
+
+## Custom Actor Types
+
+You can define custom actor types that allows a custom user, managed by your custom module, to authenticate into Medusa.
+
+For example, if you have a custom module with a `Manager` data model, you can authenticate managers with the `manager` actor type.
+
+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.
@@ -17848,8 +17918,9 @@ If the authentication is successful, the request returns a `201` response code.
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?token=123
+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"
@@ -17865,9 +17936,11 @@ 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`.
-#### Query Parameters
+#### 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.
-The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route).
+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
@@ -18282,6 +18355,256 @@ In the workflow, you:
You can use this workflow when deleting a manager, such as in an API route.
+# 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.
+
+## Authentication Methods
+
+### Register
+
+The [register method of the Auth Module's main service](https://docs.medusajs.com/references/auth/register/index.html.md) creates an auth identity that can be authenticated later.
+
+For example:
+
+```ts
+const data = await authModuleService.register(
+ "emailpass",
+ // passed to auth provider
+ {
+ // ...
+ }
+)
+```
+
+This method calls the `register` method of the provider specified in the first parameter and returns its data.
+
+### Authenticate
+
+To authenticate a user, you use the [authenticate method of the Auth Module's main service](https://docs.medusajs.com/references/auth/authenticate/index.html.md). For example:
+
+```ts
+const data = await authModuleService.authenticate(
+ "emailpass",
+ // passed to auth provider
+ {
+ // ...
+ }
+)
+```
+
+This method calls the `authenticate` method of the provider specified in the first parameter and returns its data.
+
+***
+
+## Auth Flow 1: Basic Authentication
+
+The basic authentication flow requires first using the `register` method, then the `authenticate` method:
+
+```ts
+const { success, authIdentity, error } = await authModuleService.register(
+ "emailpass",
+ // passed to auth provider
+ {
+ // ...
+ }
+)
+
+if (error) {
+ // registration failed
+ // TODO return an error
+ return
+}
+
+// later (can be another route for log-in)
+const { success, authIdentity, location } = await authModuleService.authenticate(
+ "emailpass",
+ // passed to auth provider
+ {
+ // ...
+ }
+)
+
+if (success && !location) {
+ // user is authenticated
+}
+```
+
+If `success` is true and `location` isn't set, the user is authenticated successfully, and their authentication details are available within the `authIdentity` object.
+
+The next section explains the flow if `location` is set.
+
+Check out the [AuthIdentity](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) reference for the received properties in `authIdentity`.
+
+
+
+### Auth Identity with Same Identifier
+
+If an auth identity, such as a `customer`, tries to register with an email of another auth identity, the `register` method returns an error. This can happen either if another customer is using the same email, or an admin user has the same email.
+
+There are two ways to handle this:
+
+- Consider the customer authenticated if the `authenticate` method validates that the email and password are correct. This allows admin users, for example, to authenticate as customers.
+- Return an error message to the customer, informing them that the email is already in use.
+
+***
+
+## Auth Flow 2: Third-Party Service Authentication
+
+The third-party service authentication method requires using the `authenticate` method first:
+
+```ts
+const { success, authIdentity, location } = await authModuleService.authenticate(
+ "google",
+ // passed to auth provider
+ {
+ // ...
+ }
+)
+
+if (location) {
+ // return the location for the front-end to redirect to
+}
+
+if (!success) {
+ // authentication failed
+}
+
+// authentication successful
+```
+
+If the `authenticate` method returns a `location` property, the authentication process requires the user to perform an action with a third-party service. So, you return the `location` to the front-end or client to redirect to that URL.
+
+For example, when using the `google` provider, the `location` is the URL that the user is navigated to login.
+
+
+
+### Overriding Callback URL
+
+The Google and GitHub providers allow you to override their `callbackUrl` option during authentication. This is useful when you redirect the user after authentication to a URL based on its actor type. For example, you redirect admin users and customers to different pages.
+
+```ts
+const { success, authIdentity, location } = await authModuleService.authenticate(
+ "google",
+ // passed to auth provider
+ {
+ // ...
+ callback_url: "example.com",
+ }
+)
+```
+
+### validateCallback
+
+Providers handling this authentication flow must implement the `validateCallback` method. It implements the logic to validate the authentication with the third-party service.
+
+So, once the user performs the required action with the third-party service (for example, log-in with Google), the frontend must redirect to an API route that uses the [validateCallback method of the Auth Module's main service](https://docs.medusajs.com/references/auth/validateCallback/index.html.md).
+
+The method calls the specified provider’s `validateCallback` method passing it the authentication details it received in the second parameter:
+
+```ts
+const { success, authIdentity } = await authModuleService.validateCallback(
+ "google",
+ // passed to auth provider
+ {
+ // request data, such as
+ url,
+ headers,
+ query,
+ body,
+ protocol,
+ }
+)
+
+if (success) {
+ // authentication succeeded
+}
+```
+
+For providers like Google, the `query` object contains the query parameters from the original callback URL, such as the `code` and `state` parameters.
+
+If the returned `success` property is `true`, the authentication with the third-party provider was successful.
+
+
+
+***
+
+## Reset Password
+
+To update a user's password or other authentication details, use the `updateProvider` method of the Auth Module's main service. It calls the `update` method of the specified authentication provider.
+
+For example:
+
+```ts
+const { success } = await authModuleService.updateProvider(
+ "emailpass",
+ // passed to the auth provider
+ {
+ entity_id: "user@example.com",
+ password: "supersecret",
+ }
+)
+
+if (success) {
+ // password reset successfully
+}
+```
+
+The method accepts as a first parameter the ID of the provider, and as a second parameter the data necessary to reset the password.
+
+In the example above, you use the `emailpass` provider, so you have to pass an object having an `email` and `password` properties.
+
+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/references/medusa-config#http-authMethodsPerActor-1-3/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.
@@ -18387,7 +18710,7 @@ export default async function resetPasswordTokenHandler({
const urlPrefix = actor_type === "customer" ?
"https://storefront.com" :
- "https://admin.com"
+ "https://admin.com/app"
await notificationModuleService.createNotifications({
to: email,
@@ -18462,165 +18785,159 @@ 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)
-# API Key Concepts
-
-In this document, you’ll learn about the different types of API keys, their expiration and verification.
+# Cart Concepts
-## API Key Types
+In this document, you’ll get an overview of the main concepts of a cart.
-There are two types of API keys:
+## Shipping and Billing Addresses
-- `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.
+A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/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).
+
***
-## 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.
-
-***
+## Line Items
-## Token Verification
+A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items.
-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.
+A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
+In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/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.
+## Shipping Methods
-## Summary
+A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
-The API Key Module has the following links to other modules:
+In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method.
-- [`ApiKey` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module).
+### data Property
-***
+After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments.
-## Sales Channel Module
+If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property.
-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.
+The `data` property is an object used to store custom data relevant later for fulfillment.
-
-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.
+# Promotions Adjustments in Carts
-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).
+In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
-### Retrieve with Query
+## What are Adjustment Lines?
-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`:
+An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
-### query.graph
+The [LineItemAdjustment](https://docs.medusajs.com/references/cart/models/LineItemAdjustment/index.html.md) data model represents changes on a line item, and the [ShippingMethodAdjustment](https://docs.medusajs.com/references/cart/models/ShippingMethodAdjustment/index.html.md) data model represents changes on a shipping method.
-```ts
-const { data: apiKeys } = await query.graph({
- entity: "api_key",
- fields: [
- "sales_channels.*",
- ],
-})
+
-// apiKeys.sales_channels
-```
+The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
-### useQueryGraphStep
+***
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+## Discountable Option
-// ...
+The [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
-const { data: apiKeys } = useQueryGraphStep({
- entity: "api_key",
- fields: [
- "sales_channels.*",
- ],
-})
+When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
-// apiKeys.sales_channels
-```
+***
-### Manage with Link
+## Promotion Actions
-To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
-### link.create
+Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
-```ts
-import { Modules } from "@medusajs/framework/utils"
+For example:
-// ...
+```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ ComputeActionAdjustmentLine,
+ ComputeActionItemLine,
+ ComputeActionShippingLine,
+ // ...
+} from "@medusajs/framework/types"
-await link.create({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
+// retrieve the cart
+const cart = await cartModuleService.retrieveCart("cart_123", {
+ relations: [
+ "items.adjustments",
+ "shipping_methods.adjustments",
+ ],
})
-```
-### 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",
- },
+// retrieve line item adjustments
+const lineItemAdjustments: ComputeActionItemLine[] = []
+cart.items.forEach((item) => {
+ const filteredAdjustments = item.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ lineItemAdjustments.push({
+ ...item,
+ adjustments: filteredAdjustments,
+ })
+ }
})
-```
-
-
-# Cart Concepts
-
-In this document, you’ll get an overview of the main concepts of a cart.
-
-## Shipping and Billing Addresses
-
-A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/index.html.md).
-
-
-
-***
-
-## Line Items
-
-A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items.
-
-A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
-
-In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md).
-***
+// retrieve shipping method adjustments
+const shippingMethodAdjustments: ComputeActionShippingLine[] =
+ []
+cart.shipping_methods.forEach((shippingMethod) => {
+ const filteredAdjustments =
+ shippingMethod.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ shippingMethodAdjustments.push({
+ ...shippingMethod,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
-## Shipping Methods
+// compute actions
+const actions = await promotionModuleService.computeActions(
+ ["promo_123"],
+ {
+ items: lineItemAdjustments,
+ shipping_methods: shippingMethodAdjustments,
+ }
+)
+```
-A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
+The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
-In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method.
+Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
-### data Property
+```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
+import {
+ AddItemAdjustmentAction,
+ AddShippingMethodAdjustment,
+ // ...
+} from "@medusajs/framework/types"
-After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments.
+// ...
-If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property.
+await cartModuleService.setLineItemAdjustments(
+ cart.id,
+ actions.filter(
+ (action) => action.action === "addItemAdjustment"
+ ) as AddItemAdjustmentAction[]
+)
-The `data` property is an object used to store custom data relevant later for fulfillment.
+await cartModuleService.setShippingMethodAdjustments(
+ cart.id,
+ actions.filter(
+ (action) =>
+ action.action === "addShippingMethodAdjustment"
+ ) as AddShippingMethodAdjustment[]
+)
+```
# Links between Cart Module and Other Modules
@@ -19135,184 +19452,178 @@ await cartModuleService.setLineItemTaxLines(
```
-# Promotions Adjustments in Carts
+# Fulfillment Concepts
-In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
+In this document, you’ll learn about some basic fulfillment concepts.
-## What are Adjustment Lines?
+## Fulfillment Set
-An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
+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.
-The [LineItemAdjustment](https://docs.medusajs.com/references/cart/models/LineItemAdjustment/index.html.md) data model represents changes on a line item, and the [ShippingMethodAdjustment](https://docs.medusajs.com/references/cart/models/ShippingMethodAdjustment/index.html.md) data model represents changes on a shipping method.
+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",
+ },
+ ]
+)
+```
-The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
+***
+
+## 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.
***
-## Discountable Option
+## Shipping Profile
-The [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
+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.
-When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
+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.
+
+
+# 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).
***
-## Promotion Actions
+## Fulfillment Processing by a Fulfillment Provider
-When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
+A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
-Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
+The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
-For example:
+
-```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- ComputeActionAdjustmentLine,
- ComputeActionItemLine,
- ComputeActionShippingLine,
- // ...
-} from "@medusajs/framework/types"
+***
-// retrieve the cart
-const cart = await cartModuleService.retrieveCart("cart_123", {
- relations: [
- "items.adjustments",
- "shipping_methods.adjustments",
- ],
-})
+## data Property
-// retrieve line item adjustments
-const lineItemAdjustments: ComputeActionItemLine[] = []
-cart.items.forEach((item) => {
- const filteredAdjustments = item.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- lineItemAdjustments.push({
- ...item,
- adjustments: filteredAdjustments,
- })
- }
-})
+The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
-// retrieve shipping method adjustments
-const shippingMethodAdjustments: ComputeActionShippingLine[] =
- []
-cart.shipping_methods.forEach((shippingMethod) => {
- const filteredAdjustments =
- shippingMethod.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- shippingMethodAdjustments.push({
- ...shippingMethod,
- adjustments: filteredAdjustments,
- })
- }
-})
+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.
-// compute actions
-const actions = await promotionModuleService.computeActions(
- ["promo_123"],
- {
- items: lineItemAdjustments,
- shipping_methods: shippingMethodAdjustments,
- }
-)
-```
+***
-The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
+## Fulfillment Items
-Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
+A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
-```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
-import {
- AddItemAdjustmentAction,
- AddShippingMethodAdjustment,
- // ...
-} from "@medusajs/framework/types"
+The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
-// ...
+
-await cartModuleService.setLineItemAdjustments(
- cart.id,
- actions.filter(
- (action) => action.action === "addItemAdjustment"
- ) as AddItemAdjustmentAction[]
-)
+***
-await cartModuleService.setShippingMethodAdjustments(
- cart.id,
- actions.filter(
- (action) =>
- action.action === "addShippingMethodAdjustment"
- ) as AddShippingMethodAdjustment[]
-)
-```
+## 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.
-# Customer Accounts
+***
-In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
+## Fulfillment Status
-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.
+The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
-## `has_account` Property
+- `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.
-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`.
+# Fulfillment Module Provider
-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`.
+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).
***
-## Email Uniqueness
+## Configure Fulfillment Providers
-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.
+The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
-So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
+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).
+***
-# Links between Customer Module and Other Modules
+## How to Create a Fulfillment Provider?
-This document showcases the module links defined between the Customer Module and other commerce modules.
+Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
-## Summary
-The Customer Module has the following links to other modules:
+# Links between Fulfillment Module and 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.
+This document showcases the module links defined between the Fulfillment Module and other commerce modules.
-- [`Customer` data model \<> `AccountHolder` data model of Payment Module](#payment-module).
-- [`Cart` data model of Cart Module \<> `Customer` data model](#cart-module). (Read-only).
-- [`Order` data model of Order Module \<> `Customer` data model](#order-module). (Read-only).
+## Summary
+
+The Fulfillment Module has the following links to other modules:
+
+- [`Order` data model of the Order Module \<> `Fulfillment` data model](#order-module).
+- [`Return` data model of the Order Module \<> `Fulfillment` data model](#order-module).
+- [`PriceSet` data model of the Pricing Module \<> `ShippingOption` data model](#pricing-module).
+- [`Product` data model of the Product Module \<> `ShippingProfile` data model](#product-module).
+- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentProvider` data model](#stock-location-module).
+- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentSet` data model](#stock-location-module).
***
-## Payment Module
+## Order 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.
+The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
-This link is available starting from Medusa `v2.5.0`.
+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 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`:
+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: customers } = await query.graph({
- entity: "customer",
+const { data: fulfillments } = await query.graph({
+ entity: "fulfillment",
fields: [
- "account_holder.*",
+ "order.*",
],
})
-// customers.account_holder
+// fulfillments.order
```
### useQueryGraphStep
@@ -19322,19 +19633,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
+const { data: fulfillments } = useQueryGraphStep({
+ entity: "fulfillment",
fields: [
- "account_holder.*",
+ "order.*",
],
})
-// customers.account_holder
+// fulfillments.order
```
### 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):
+To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
### link.create
@@ -19344,11 +19655,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
+ [Modules.ORDER]: {
+ order_id: "order_123",
},
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
},
})
```
@@ -19356,41 +19667,46 @@ await link.create({
### createRemoteLinkStep
```ts
+import { Modules } from "@medusajs/framework/utils"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
+ [Modules.ORDER]: {
+ order_id: "order_123",
},
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
},
})
```
***
-## Cart Module
+## Pricing Module
-Medusa defines a read-only link between the `Customer` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a customer's carts, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model.
+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 a customer's carts with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
+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: customers } = await query.graph({
- entity: "customer",
+const { data: shippingOptions } = await query.graph({
+ entity: "shipping_option",
fields: [
- "carts.*",
+ "price_set.*",
],
})
-// customers.carts
+// shippingOptions.price_set
```
### useQueryGraphStep
@@ -19400,37 +19716,78 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
+const { data: shippingOptions } = useQueryGraphStep({
+ entity: "shipping_option",
fields: [
- "carts.*",
+ "price_set.*",
],
})
-// customers.carts
+// shippingOptions.price_set
```
-***
+### Manage with Link
-## Order Module
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-Medusa defines a read-only link between the `Customer` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a customer's orders, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model.
+### link.create
-### Retrieve with Query
+```ts
+import { Modules } from "@medusajs/framework/utils"
-To retrieve a customer's orders with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
+// ...
-### query.graph
+await link.create({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
```ts
-const { data: customers } = await query.graph({
- entity: "customer",
+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: [
- "orders.*",
+ "products.*",
],
})
-// customers.orders
+// shippingProfiles.products
```
### useQueryGraphStep
@@ -19440,16 +19797,314 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
+const { data: shippingProfiles } = useQueryGraphStep({
+ entity: "shipping_profile",
fields: [
- "orders.*",
+ "products.*",
],
})
-// customers.orders
+// 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",
+ },
+})
+```
+
+
+# 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.
+
+
+# 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.
+
+## Product Variant Creation
+
+When a product variant is created and its `manage_inventory` property's value is `true`, the Medusa application creates an inventory item associated with that product variant.
+
+This flow is implemented within the [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
+
+
+
+***
+
+## Add to Cart
+
+When a product variant with `manage_inventory` set to `true` is added to cart, the Medusa application checks whether there's sufficient stocked quantity. If not, an error is thrown and the product variant won't be added to the cart.
+
+This flow is implemented within the [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
+
+
+
+***
+
+## Order Placed
+
+When an order is placed, the Medusa application creates a reservation item for each product variant with `manage_inventory` set to `true`.
+
+This flow is implemented within the [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
+
+
+
+***
+
+## Order Fulfillment
+
+When an item in an order is fulfilled and the associated variant has its `manage_inventory` property set to `true`, the Medusa application:
+
+- Subtracts the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item.
+- Resets the `reserved_quantity` to `0`.
+- Deletes the associated reservation item.
+
+This flow is implemented within the [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
+
+
+
+***
+
+## Order Return
+
+When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity.
+
+This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
+
+
+
+### Dismissed Returned Items
+
+If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level.
+
# Inventory Kits
@@ -19839,67 +20494,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).
-# Inventory Module in Medusa Flows
-
-This document explains how the Inventory Module is used within the Medusa application's flows.
-
-## Product Variant Creation
-
-When a product variant is created and its `manage_inventory` property's value is `true`, the Medusa application creates an inventory item associated with that product variant.
-
-This flow is implemented within the [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
-
-
-
-***
-
-## Add to Cart
-
-When a product variant with `manage_inventory` set to `true` is added to cart, the Medusa application checks whether there's sufficient stocked quantity. If not, an error is thrown and the product variant won't be added to the cart.
-
-This flow is implemented within the [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
-
-
-
-***
-
-## Order Placed
-
-When an order is placed, the Medusa application creates a reservation item for each product variant with `manage_inventory` set to `true`.
-
-This flow is implemented within the [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md)
-
-
-
-***
-
-## Order Fulfillment
-
-When an item in an order is fulfilled and the associated variant has its `manage_inventory` property set to `true`, the Medusa application:
-
-- Subtracts the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item.
-- Resets the `reserved_quantity` to `0`.
-- Deletes the associated reservation item.
-
-This flow is implemented within the [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
-
-
-
-***
-
-## Order Return
-
-When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity.
-
-This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
-
-
-
-### Dismissed Returned Items
-
-If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level.
-
-
# Inventory Concepts
In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
@@ -20082,865 +20676,546 @@ const { data: inventoryLevels } = useQueryGraphStep({
```
-# Links between Currency Module and Other Modules
+# Order Claim
-This document showcases the module links defined between the Currency Module and other commerce modules.
+In this document, you’ll learn about order claims.
-## Summary
+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.
-The Currency Module has the following links to other modules:
+## What is a Claim?
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
-- [`Currency` data model of Store Module \<> `Currency` data model of Currency Module](#store-module). (Read-only).
+The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim.
***
-## 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.
+## Claim Type
-Instead, Medusa defines a read-only link between the Currency Module's `Currency` data model and the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'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` data model in the Store Module.
+The `Claim` data model has a `type` property whose value indicates the type of the claim:
-### Retrieve with Query
+- `refund`: the items are returned, and the customer is refunded.
+- `replace`: the items are returned, and the customer receives new items.
-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
+## Old and Replacement Items
-```ts
-const { data: stores } = await query.graph({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
+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.
-// stores.supported_currencies
-```
+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).
-### useQueryGraphStep
+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).
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+***
-// ...
+## Claim Shipping Methods
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
- ],
-})
+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).
-// stores.supported_currencies
-```
+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).
+***
-# Fulfillment Module Provider
+## Claim Refund
-In this document, you’ll learn what a fulfillment module provider is.
+If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
-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.
+The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
-## What’s a Fulfillment Module Provider?
+***
-A fulfillment module provider handles fulfilling items, typically using a third-party integration.
+## How Claims Impact an Order’s Version
-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).
+When a claim is confirmed, the order’s version is incremented.
-***
-## Configure Fulfillment Providers
+# Order Edit
-The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
+In this document, you'll learn about order edits.
-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).
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/edit/index.html.md) to learn how to edit an order's items using the dashboard.
-***
+## What is an Order Edit?
-## How to Create a Fulfillment Provider?
+A merchant can edit an order to add new items or change the quantity of existing items in the order.
-Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
+An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md).
+The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making.
-# Fulfillment Concepts
+In the case of an order edit, the `OrderChange`'s type is `edit`.
-In this document, you’ll learn about some basic fulfillment concepts.
+***
-## Fulfillment Set
+## Add Items in an Order Edit
-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.
+When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md).
-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.
+Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added.
-```ts
-const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets(
- [
- {
- name: "Shipping",
- type: "shipping",
- },
- {
- name: "Pick-up",
- type: "pick-up",
- },
- ]
-)
-```
+So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored.
***
-## Service Zone
+## Update Items in an Order Edit
-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 merchant can update an existing item's quantity or price.
-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.
+This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored.
-
+***
-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 Methods of New Items in the Edit
+
+Adding new items to the order requires adding shipping methods for those items.
+
+These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD`
***
-## Shipping Profile
+## How Order Edits Impact an Order’s Version
-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.
+When an order edit is confirmed, the order’s version is incremented.
-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.
+***
+## Payments and Refunds for Order Edit Changes
-# Item Fulfillment
+Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order.
-In this document, you’ll learn about the concepts of item fulfillment.
+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).
-## 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).
+# Order Concepts
-***
+In this document, you’ll learn about orders and related concepts
-## Fulfillment Processing by a Fulfillment Provider
+## Order Items
-A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
+The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items.
-The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
+
-
+### Item’s Product Details
+
+The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes.
***
-## data Property
+## Order’s Shipping Method
-The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
+An order has one or more shipping methods used to handle item shipment.
-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.
+Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md).
-***
+
-## Fulfillment Items
+### data Property
-A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
+When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
-The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
+The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment.
-
+The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items.
***
-## Fulfillment Label
+## Order Totals
-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.
+The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md).
***
-## Fulfillment Status
+## Order Payments
-The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
+Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-- `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.
+An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount.
+Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md).
-# Links between Fulfillment Module and Other Modules
-This document showcases the module links defined between the Fulfillment Module and other commerce modules.
+# Order Exchange
-## Summary
+In this document, you’ll learn about order exchanges.
-The Fulfillment Module has the following links to other modules:
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/exchanges/index.html.md) to learn how to manage an order's exchanges using the dashboard.
-- [`Order` data model of the Order Module \<> `Fulfillment` data model](#order-module).
-- [`Return` data model of the Order Module \<> `Fulfillment` data model](#order-module).
-- [`PriceSet` data model of the Pricing Module \<> `ShippingOption` data model](#pricing-module).
-- [`Product` data model of the Product Module \<> `ShippingProfile` data model](#product-module).
-- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentProvider` data model](#stock-location-module).
-- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentSet` data model](#stock-location-module).
+## What is an Exchange?
-***
+An exchange is the replacement of an item that the customer ordered with another.
-## Order Module
+A merchant creates the exchange, specifying the items to be replaced and the new items to be sent.
-The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
+The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange.
-Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
+***
-
+## Returned and New Items
-A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
+When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back 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).
-### Retrieve with Query
+The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer.
-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`.
+## Exchange Shipping Methods
-### query.graph
+An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
-```ts
-const { data: fulfillments } = await query.graph({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
+The shipping methods for the returned items are associated with the exchange'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).
-// fulfillments.order
-```
+***
-### useQueryGraphStep
+## Exchange Payment
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+The `Exchange` data model has a `difference_due` property that stores the outstanding amount.
-// ...
+|Condition|Result|
+|---|---|---|
+|\`difference\_due \< 0\`|Merchant owes the customer a refund of the |
+|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the |
+|\`difference\_due = 0\`|No payment processing is required.|
-const { data: fulfillments } = useQueryGraphStep({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
+Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-// fulfillments.order
-```
+***
-### Manage with Link
+## How Exchanges Impact an Order’s Version
-To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+When an exchange is confirmed, the order’s version is incremented.
-### link.create
-```ts
-import { Modules } from "@medusajs/framework/utils"
+# Order Change
-// ...
+In this document, you'll learn about the Order Change data model and possible actions in it.
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
+## OrderChange Data Model
-### createRemoteLinkStep
+The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit.
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+Its `change_type` property indicates what the order change is created for:
-// ...
+1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md).
+2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md).
+3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md).
+4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
+Once the order change is confirmed, its changes are applied on the order.
***
-## Pricing Module
+## Order Change Actions
-The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context.
+The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md).
-Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
+The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action.
-
+The following table lists the possible `action` values that Medusa uses and what `details` they carry.
-### Retrieve with Query
+|Action|Description|Details|
+|---|---|---|---|---|
+|\`ITEM\_ADD\`|Add an item to the order.|\`details\`|
+|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`|
+|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`|
+|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`|
+|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`|
+|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
+|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
+|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`|
-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
+# Promotions Adjustments in Orders
-```ts
-const { data: shippingOptions } = await query.graph({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
+In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
-// shippingOptions.price_set
-```
+## What are Adjustment Lines?
-### useQueryGraphStep
+An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order.
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method.
-// ...
+
-const { data: shippingOptions } = useQueryGraphStep({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
+The `amount` property of the adjustment line indicates the amount to be discounted from the original amount.
-// shippingOptions.price_set
-```
+The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
-### 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):
+## Discountable Option
-### link.create
+The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
-```ts
-import { Modules } from "@medusajs/framework/utils"
+When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
-// ...
+***
-await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
+## Promotion Actions
-### createRemoteLinkStep
+When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
+
+```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
+import {
+ ComputeActionAdjustmentLine,
+ ComputeActionItemLine,
+ ComputeActionShippingLine,
+ // ...
+} from "@medusajs/framework/types"
// ...
-createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
+// retrieve the order
+const order = await orderModuleService.retrieveOrder("ord_123", {
+ relations: [
+ "items.item.adjustments",
+ "shipping_methods.shipping_method.adjustments",
+ ],
+})
+// retrieve the line item adjustments
+const lineItemAdjustments: ComputeActionItemLine[] = []
+order.items.forEach((item) => {
+ const filteredAdjustments = item.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ lineItemAdjustments.push({
+ ...item,
+ ...item.detail,
+ adjustments: filteredAdjustments,
+ })
+ }
})
-```
-
-***
-## Product Module
+//retrieve shipping method adjustments
+const shippingMethodAdjustments: ComputeActionShippingLine[] =
+ []
+order.shipping_methods.forEach((shippingMethod) => {
+ const filteredAdjustments =
+ shippingMethod.adjustments?.filter(
+ (adjustment) => adjustment.code !== undefined
+ ) as unknown as ComputeActionAdjustmentLine[]
+ if (filteredAdjustments.length) {
+ shippingMethodAdjustments.push({
+ ...shippingMethod,
+ adjustments: filteredAdjustments,
+ })
+ }
+})
-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.
+// compute actions
+const actions = await promotionModuleService.computeActions(
+ ["promo_123"],
+ {
+ items: lineItemAdjustments,
+ shipping_methods: shippingMethodAdjustments,
+ // TODO infer from cart or region
+ currency_code: "usd",
+ }
+)
+```
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
+The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
-### Retrieve with Query
+Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments.
-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`:
+```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import {
+ AddItemAdjustmentAction,
+ AddShippingMethodAdjustment,
+ // ...
+} from "@medusajs/framework/types"
-### query.graph
+// ...
-```ts
-const { data: shippingProfiles } = await query.graph({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
+await orderModuleService.setOrderLineItemAdjustments(
+ order.id,
+ actions.filter(
+ (action) => action.action === "addItemAdjustment"
+ ) as AddItemAdjustmentAction[]
+)
-// shippingProfiles.products
+await orderModuleService.setOrderShippingMethodAdjustments(
+ order.id,
+ actions.filter(
+ (action) =>
+ action.action === "addShippingMethodAdjustment"
+ ) as AddShippingMethodAdjustment[]
+)
```
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-// ...
+# Order Return
-const { data: shippingProfiles } = useQueryGraphStep({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
+In this document, you’ll learn about order returns.
-// shippingProfiles.products
-```
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/returns/index.html.md) to learn how to manage an order's returns using the dashboard.
-### Manage with Link
+## What is a Return?
-To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md).
-### link.create
+A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow.
-```ts
-import { Modules } from "@medusajs/framework/utils"
+
-// ...
+Once the merchant receives the returned items, they mark the return as received.
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
+***
-### createRemoteLinkStep
+## Returned Items
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem).
-// ...
+The `ReturnItem` model has two properties storing the item's quantity:
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
+1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity.
+2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity.
***
-## Stock Location Module
+## Return Shipping Methods
-The Stock Location Module provides features to manage stock locations in a store.
+A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod).
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location.
+In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled.
-
+***
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
+## Refund Payment
-
+The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer.
-### Retrieve with Query
+The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return.
-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`.
+## Returns in Exchanges and Claims
-### query.graph
+When a merchant creates an exchange or a claim, it includes returning items from the customer.
-```ts
-const { data: fulfillmentSets } = await query.graph({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
+The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for.
-// fulfillmentSets.location
-```
+***
-### useQueryGraphStep
+## How Returns Impact an Order’s Version
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+The order’s version is incremented when:
-// ...
+1. A return is requested.
+2. A return is marked as received.
-const { data: fulfillmentSets } = useQueryGraphStep({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
-// fulfillmentSets.location
-```
+# Tax Lines in Order Module
-### Manage with Link
+In this document, you’ll learn about tax lines in an order.
-To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+## What are Tax Lines?
-### link.create
+A tax line indicates the tax rate of a line item or a shipping method.
-```ts
-import { Modules } from "@medusajs/framework/utils"
+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.
-// ...
+
-await link.create({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
+***
-### createRemoteLinkStep
+## Tax Inclusivity
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+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.
-createRemoteLinkStep({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
+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.
-# Shipping Option
+
-In this document, you’ll learn about shipping options and their rules.
+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`.
-## 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.
+# Transactions
-When the customer places their order, they choose a shipping option to be used to fulfill their items.
+In this document, you’ll learn about an order’s transactions and its use.
-A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md).
+## What is a Transaction?
-***
+A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/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).
+The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts.
-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.
+Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required.
***
-## 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.
-
-
-# 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"
+## Checking Outstanding Amount
-// ...
+The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order.
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/fulfillment",
- options: {
- providers: [
- {
- resolve: `@medusajs/medusa/fulfillment-manual`,
- id: "manual",
- options: {
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
+```json
+{
+ "totals": {
+ "total": 30,
+ "subtotal": 30,
+ // ...
+ }
+}
```
-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.
-
-
-# Order Concepts
-
-In this document, you’ll learn about orders and related concepts
-
-## Order Items
-
-The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items.
-
-
-
-### Item’s Product Details
-
-The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes.
-
-***
-
-## Order’s Shipping Method
-
-An order has one or more shipping methods used to handle item shipment.
-
-Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md).
-
-
-
-### data Property
-
-When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
-
-The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment.
-
-The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items.
-
-***
-
-## Order Totals
-
-The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md).
-
-***
-
-## Order Payments
-
-Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-
-An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount.
-
-Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/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 Edit
-
-In this document, you'll learn about order edits.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/edit/index.html.md) to learn how to edit an order's items using the dashboard.
-
-## What is an Order Edit?
-
-A merchant can edit an order to add new items or change the quantity of existing items in the order.
-
-An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md).
-
-The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making.
-
-In the case of an order edit, the `OrderChange`'s type is `edit`.
-
-***
-
-## Add Items in an Order Edit
-
-When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md).
-
-Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added.
-
-So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored.
-
-***
-
-## Update Items in an Order Edit
-
-A merchant can update an existing item's quantity or price.
-
-This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored.
-
-***
-
-## Shipping Methods of New Items in the Edit
-
-Adding new items to the order requires adding shipping methods for those items.
-
-These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD`
-
-***
-
-## How Order Edits Impact an Order’s Version
+To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
-When an order edit is confirmed, the order’s version is incremented.
+|Condition|Result|
+|---|---|---|
+|summary’s total - transaction amounts total = 0|There’s no outstanding amount.|
+|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.|
+|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.|
***
-## Payments and Refunds for Order Edit Changes
-
-Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order.
-
-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 Exchange
-
-In this document, you’ll learn about order exchanges.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/exchanges/index.html.md) to learn how to manage an order's exchanges using the dashboard.
+## Transaction Reference
-## What is an Exchange?
+The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md).
-An exchange is the replacement of an item that the customer ordered with another.
+The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details:
-A merchant creates the exchange, specifying the items to be replaced and the new items to be sent.
+- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module.
+- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
-The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange.
-***
+# Order Versioning
-## Returned and New Items
+In this document, you’ll learn how an order and its details are versioned.
-When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer.
+## What's Versioning?
-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).
+Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime.
-The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer.
+When changes are made on an order, such as an item is added or returned, the order's version changes.
***
-## Exchange Shipping Methods
+## version Property
-An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md).
+The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
-The shipping methods for the returned items are associated with the exchange'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).
+Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to.
***
-## Exchange Payment
-
-The `Exchange` data model has a `difference_due` property that stores the outstanding amount.
-
-|Condition|Result|
-|---|---|---|
-|\`difference\_due \< 0\`|Merchant owes the customer a refund of the |
-|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the |
-|\`difference\_due = 0\`|No payment processing is required.|
-
-Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
+## How the Version Changes
-***
+When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
-## How Exchanges Impact an Order’s Version
+1. The version of the order and its summary is incremented.
+2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version.
-When an exchange is confirmed, the order’s version is incremented.
+When the order is retrieved, only the related data having the same version is retrieved.
# Links between Order Module and Other Modules
@@ -21465,382 +21740,140 @@ const { data: orders } = useQueryGraphStep({
```
-# Order Versioning
-
-In this document, you’ll learn how an order and its details are versioned.
-
-## What's Versioning?
-
-Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime.
+# Pricing Concepts
-When changes are made on an order, such as an item is added or returned, the order's version changes.
+In this document, you’ll learn about the main concepts in the Pricing Module.
-***
+## Price Set
-## version Property
+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).
-The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
+Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
-Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to.
+
***
-## How the Version Changes
+## Price List
-When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
+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.
-1. The version of the order and its summary is incremented.
-2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version.
+A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
-When the order is retrieved, only the related data having the same version is retrieved.
+Its associated prices are represented by the `Price` data model.
-# Promotions Adjustments in Orders
+# Links between Pricing Module and Other Modules
-In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
+This document showcases the module links defined between the Pricing Module and other commerce modules.
-## What are Adjustment Lines?
+## Summary
-An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order.
+The Pricing Module has the following links to other modules:
-The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method.
+- [`ShippingOption` data model of Fulfillment Module \<> `PriceSet` data model](#fulfillment-module).
+- [`ProductVariant` data model of Product Module \<> `PriceSet` data model](#product-module).
-
+***
-The `amount` property of the adjustment line indicates the amount to be discounted from the original amount.
+## Fulfillment Module
-The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
+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.
-## Discountable Option
+
-The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
+### Retrieve with Query
-When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
+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
-## Promotion Actions
+```ts
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
+ ],
+})
-When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods.
+// priceSets.shipping_option
+```
-Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md).
+### useQueryGraphStep
-```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
-import {
- ComputeActionAdjustmentLine,
- ComputeActionItemLine,
- ComputeActionShippingLine,
- // ...
-} from "@medusajs/framework/types"
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-// retrieve the order
-const order = await orderModuleService.retrieveOrder("ord_123", {
- relations: [
- "items.item.adjustments",
- "shipping_methods.shipping_method.adjustments",
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
],
})
-// retrieve the line item adjustments
-const lineItemAdjustments: ComputeActionItemLine[] = []
-order.items.forEach((item) => {
- const filteredAdjustments = item.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- lineItemAdjustments.push({
- ...item,
- ...item.detail,
- adjustments: filteredAdjustments,
- })
- }
-})
-
-//retrieve shipping method adjustments
-const shippingMethodAdjustments: ComputeActionShippingLine[] =
- []
-order.shipping_methods.forEach((shippingMethod) => {
- const filteredAdjustments =
- shippingMethod.adjustments?.filter(
- (adjustment) => adjustment.code !== undefined
- ) as unknown as ComputeActionAdjustmentLine[]
- if (filteredAdjustments.length) {
- shippingMethodAdjustments.push({
- ...shippingMethod,
- adjustments: filteredAdjustments,
- })
- }
-})
-// compute actions
-const actions = await promotionModuleService.computeActions(
- ["promo_123"],
- {
- items: lineItemAdjustments,
- shipping_methods: shippingMethodAdjustments,
- // TODO infer from cart or region
- currency_code: "usd",
- }
-)
+// priceSets.shipping_option
```
-The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
+### Manage with Link
-Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments.
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import {
- AddItemAdjustmentAction,
- AddShippingMethodAdjustment,
- // ...
-} from "@medusajs/framework/types"
+### link.create
-// ...
+```ts
+import { Modules } from "@medusajs/framework/utils"
-await orderModuleService.setOrderLineItemAdjustments(
- order.id,
- actions.filter(
- (action) => action.action === "addItemAdjustment"
- ) as AddItemAdjustmentAction[]
-)
+// ...
-await orderModuleService.setOrderShippingMethodAdjustments(
- order.id,
- actions.filter(
- (action) =>
- action.action === "addShippingMethodAdjustment"
- ) as AddShippingMethodAdjustment[]
-)
+await link.create({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
```
+### createRemoteLinkStep
-# Order Change
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-In this document, you'll learn about the Order Change data model and possible actions in it.
+// ...
-## OrderChange Data Model
+createRemoteLinkStep({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
-The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit.
+***
-Its `change_type` property indicates what the order change is created for:
+## Product Module
-1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md).
-2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md).
-3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md).
-4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md).
+The Product Module doesn't store or manage the prices of product variants.
-Once the order change is confirmed, its changes are applied on the order.
+Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
-***
+
-## Order Change Actions
+So, when you want to add prices for a product variant, you create a price set and add the prices to it.
-The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md).
+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.
-The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action.
+### Retrieve with Query
-The following table lists the possible `action` values that Medusa uses and what `details` they carry.
-
-|Action|Description|Details|
-|---|---|---|---|---|
-|\`ITEM\_ADD\`|Add an item to the order.|\`details\`|
-|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`|
-|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`|
-|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`|
-|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`|
-|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
-|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the |
-|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`|
-
-
-# Order Return
-
-In this document, you’ll learn about order returns.
-
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/orders/returns/index.html.md) to learn how to manage an order's returns using the dashboard.
-
-## What is a Return?
-
-A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md).
-
-A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow.
-
-
-
-Once the merchant receives the returned items, they mark the return as received.
-
-***
-
-## Returned Items
-
-The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem).
-
-The `ReturnItem` model has two properties storing the item's quantity:
-
-1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity.
-2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity.
-
-***
-
-## Return Shipping Methods
-
-A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod).
-
-In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled.
-
-***
-
-## Refund Payment
-
-The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer.
-
-The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return.
-
-***
-
-## Returns in Exchanges and Claims
-
-When a merchant creates an exchange or a claim, it includes returning items from the customer.
-
-The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for.
-
-***
-
-## How Returns Impact an Order’s Version
-
-The order’s version is incremented when:
-
-1. A return is requested.
-2. A return is marked as received.
-
-
-# Transactions
-
-In this document, you’ll learn about an order’s transactions and its use.
-
-## What is a Transaction?
-
-A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md).
-
-The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts.
-
-Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required.
-
-***
-
-## Checking Outstanding Amount
-
-The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order.
-
-```json
-{
- "totals": {
- "total": 30,
- "subtotal": 30,
- // ...
- }
-}
-```
-
-To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
-
-|Condition|Result|
-|---|---|---|
-|summary’s total - transaction amounts total = 0|There’s no outstanding amount.|
-|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.|
-|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.|
-
-***
-
-## Transaction Reference
-
-The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md).
-
-The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details:
-
-- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module.
-- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
-
-
-# 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`.
-
-
-# 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:
-
-- [`ShippingOption` data model of Fulfillment Module \<> `PriceSet` data model](#fulfillment-module).
-- [`ProductVariant` data model of Product Module \<> `PriceSet` data model](#product-module).
-
-***
-
-## 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`:
+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
@@ -21848,11 +21881,11 @@ To retrieve the shipping option of a price set with [Query](https://docs.medusaj
const { data: priceSets } = await query.graph({
entity: "price_set",
fields: [
- "shipping_option.*",
+ "variant.*",
],
})
-// priceSets.shipping_option
+// priceSets.variant
```
### useQueryGraphStep
@@ -21865,16 +21898,16 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
const { data: priceSets } = useQueryGraphStep({
entity: "price_set",
fields: [
- "shipping_option.*",
+ "variant.*",
],
})
-// priceSets.shipping_option
+// priceSets.variant
```
### 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):
+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
@@ -21884,8 +21917,8 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
[Modules.PRICING]: {
price_set_id: "pset_123",
@@ -21902,8 +21935,8 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
[Modules.PRICING]: {
price_set_id: "pset_123",
@@ -21911,92 +21944,36 @@ createRemoteLinkStep({
})
```
-***
-
-## 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
+# Price Rules
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+In this document, you'll learn about price rules for price sets and price lists.
-// ...
+## Price Rule
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
- fields: [
- "variant.*",
- ],
-})
+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).
-// priceSets.variant
-```
+The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
-### Manage with Link
+For exmaple, you create a price restricted to `10557` zip codes.
-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
+A price can have multiple price rules.
-```ts
-import { Modules } from "@medusajs/framework/utils"
+For example, a price can be restricted by a region and a zip code.
-// ...
+
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
+***
-### createRemoteLinkStep
+## Price List Rules
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+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.
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
+
# Prices Calculation
@@ -22192,35 +22169,45 @@ const price = await pricingModuleService.calculatePrices(
### Result
-# Price Rules
+# Account Holders and Saved Payment Methods
-In this document, you'll learn about price rules for price sets and price lists.
+In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
-## Price Rule
+Account holders are available starting from Medusa `v2.5.0`.
-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).
+## What's an Account Holder?
-The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
+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.
-For exmaple, you create a price restricted to `10557` zip codes.
+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 price can have multiple price rules.
+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.
-For example, a price can be restricted by a region and a zip code.
+***
-
+## 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).
***
-## Price List Rules
+## Account Holder in Medusa Payment Flows
-Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
+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.
-The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
+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).
# Tax-Inclusive Pricing
@@ -22291,91 +22278,83 @@ A region’s price preference’s `is_tax_inclusive`'s value takes higher preced
- and the region has a price preference
-# Links between Product Module and Other Modules
+# Payment Collection
-This document showcases the module links defined between the Product Module and other commerce modules.
+In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
-## Summary
+## What's a Payment Collection?
-The Product Module has the following links to other modules:
+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).
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
-- [`Product` data model \<> `Cart` data model of Cart Module](#cart-module). (Read-only).
-- [`Product` data model \<> `ShippingProfile` data model of Fulfillment Module](#fulfillment-module).
-- [`ProductVariant` data model \<> `InventoryItem` data model of Inventory Module](#inventory-module).
-- [`Product` data model \<> `Order` data model of Order Module](#order-module). (Read-only).
-- [`ProductVariant` data model \<> `PriceSet` data model of Pricing Module](#pricing-module).
-- [`Product` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module).
+- 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.
***
-## Cart Module
+## Multiple Payments
-Medusa defines read-only links between:
+The payment collection supports multiple payment sessions and payments.
-- The `Product` 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. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model.
-- 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. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model.
+You can use this to accept payments in increments or split payments across payment providers.
-### Retrieve with Query
+
-To retrieve the line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `line_items.*` in `fields`:
+***
-To retrieve the line items of a product, pass `line_items.*` in `fields`.
+## Usage with the Cart Module
-### query.graph
+The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
-```ts
-const { data: variants } = await query.graph({
- entity: "variant",
- fields: [
- "line_items.*",
- ],
-})
+During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
-// variants.line_items
-```
+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).
-### useQueryGraphStep
+
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-// ...
+# Links between Payment Module and Other Modules
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "line_items.*",
- ],
-})
+This document showcases the module links defined between the Payment Module and other commerce modules.
-// variants.line_items
-```
+## Summary
+
+The Payment Module has the following links to other modules:
+
+- [`Cart` data model of Cart Module \<> `PaymentCollection` data model](#cart-module).
+- [`Customer` data model of Customer Module \<> `AccountHolder` data model](#customer-module).
+- [`Order` data model of Order Module \<> `PaymentCollection` data model](#order-module).
+- [`OrderClaim` data model of Order Module \<> `PaymentCollection` data model](#order-module).
+- [`OrderExchange` data model of Order Module \<> `PaymentCollection` data model](#order-module).
+- [`Region` data model of Region Module \<> `PaymentProvider` data model](#region-module).
***
-## Fulfillment Module
+## Cart 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.
+The Cart Module provides cart-related features, but not payment processing.
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
+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 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`:
+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: products } = await query.graph({
- entity: "product",
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
fields: [
- "shipping_profile.*",
+ "cart.*",
],
})
-// products.shipping_profile
+// paymentCollections.cart
```
### useQueryGraphStep
@@ -22385,19 +22364,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: products } = useQueryGraphStep({
- entity: "product",
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
fields: [
- "shipping_profile.*",
+ "cart.*",
],
})
-// products.shipping_profile
+// paymentCollections.cart
```
### 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):
+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
@@ -22407,11 +22386,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.CART]: {
+ cart_id: "cart_123",
},
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
},
})
```
@@ -22419,50 +22398,43 @@ await link.create({
### createRemoteLinkStep
```ts
-import { Modules } from "@medusajs/framework/utils"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.CART]: {
+ cart_id: "cart_123",
},
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_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.
-
-
+## Customer Module
-When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
+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.
-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).
+This link is available starting from Medusa `v2.5.0`.
### 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`:
+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: variants } = await query.graph({
- entity: "variant",
+const { data: accountHolders } = await query.graph({
+ entity: "account_holder",
fields: [
- "inventory_items.*",
+ "customer.*",
],
})
-// variants.inventory_items
+// accountHolders.customer
```
### useQueryGraphStep
@@ -22472,19 +22444,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
+const { data: accountHolders } = useQueryGraphStep({
+ entity: "account_holder",
fields: [
- "inventory_items.*",
+ "customer.*",
],
})
-// variants.inventory_items
+// accountHolders.customer
```
### 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):
+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
@@ -22494,11 +22466,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
},
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
},
})
```
@@ -22506,17 +22478,16 @@ await link.create({
### createRemoteLinkStep
```ts
-import { Modules } from "@medusajs/framework/utils"
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
},
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
},
})
```
@@ -22525,74 +22496,27 @@ createRemoteLinkStep({
## Order Module
-Medusa defines read-only links between:
+An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
-- the `Product` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model.
-- the `ProductVariant` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model.
+So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+
+
### Retrieve with Query
-To retrieve the order line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order_items.*` in `fields`:
-
-To retrieve a product's order line items, pass `order_items.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: variants } = await query.graph({
- entity: "variant",
- fields: [
- "order_items.*",
- ],
-})
-
-// variants.order_items
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "order_items.*",
- ],
-})
-
-// variants.order_items
-```
-
-***
-
-## 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`:
+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: variants } = await query.graph({
- entity: "variant",
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
fields: [
- "price_set.*",
+ "order.*",
],
})
-// variants.price_set
+// paymentCollections.order
```
### useQueryGraphStep
@@ -22602,19 +22526,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
fields: [
- "price_set.*",
+ "order.*",
],
})
-// variants.price_set
+// paymentCollections.order
```
### 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):
+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
@@ -22624,11 +22548,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
+ [Modules.ORDER]: {
+ order_id: "order_123",
},
- [Modules.PRICING]: {
- price_set_id: "pset_123",
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
},
})
```
@@ -22642,40 +22566,40 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
+ [Modules.ORDER]: {
+ order_id: "order_123",
},
- [Modules.PRICING]: {
- price_set_id: "pset_123",
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
},
})
```
***
-## Sales Channel Module
+## Region Module
-The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
+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.
-Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
+
-
+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 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`:
+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: products } = await query.graph({
- entity: "product",
+const { data: paymentProviders } = await query.graph({
+ entity: "payment_provider",
fields: [
- "sales_channels.*",
+ "regions.*",
],
})
-// products.sales_channels
+// paymentProviders.regions
```
### useQueryGraphStep
@@ -22685,19 +22609,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: products } = useQueryGraphStep({
- entity: "product",
+const { data: paymentProviders } = useQueryGraphStep({
+ entity: "payment_provider",
fields: [
- "sales_channels.*",
+ "regions.*",
],
})
-// products.sales_channels
+// paymentProviders.regions
```
### 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):
+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
@@ -22707,11 +22631,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.REGION]: {
+ region_id: "reg_123",
},
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
},
})
```
@@ -22725,172 +22649,236 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.REGION]: {
+ region_id: "reg_123",
},
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
},
})
```
-# 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
+# Payment Module Options
-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.
+In this document, you'll learn about the options of the Payment Module.
-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.
+## All Module Options
-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.
+|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|-|
***
-## 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.
-
-
+## providers Option
-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.
+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.
-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).
+For example:
-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.
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
-
+// ...
-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).
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/payment",
+ options: {
+ providers: [
+ {
+ resolve: "@medusajs/medusa/payment-stripe",
+ id: "stripe",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
-### Product Inventory in Storefronts
+The `providers` option is an array of objects that accept the following properties:
-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.
+- `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.
-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.
+# 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.
-## Variant Back Orders
+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).
-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.
+## Flow Overview
-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
+## 1. Create a Payment Collection
-The following guides provide more details on inventory management in the Medusa application:
+A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
-- [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).
+For example:
+### Using Workflow
-# Account Holders and Saved Payment Methods
+```ts
+import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
-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`.
+await createPaymentCollectionForCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: "cart_123",
+ },
+ })
+```
-## What's an Account Holder?
+### Using Service
-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.
+```ts
+const paymentCollection =
+ await paymentModuleService.createPaymentCollections({
+ currency_code: "usd",
+ amount: 5000,
+ })
+```
-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.
+## 2. Create Payment Sessions
-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.
+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.
-## Save Payment Methods
+For example:
-If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+### Using Workflow
-- `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.
+```ts
+import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
-Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
+// ...
-***
+const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
+ .run({
+ input: {
+ payment_collection_id: "paycol_123",
+ provider_id: "stripe",
+ },
+ })
+```
-## Account Holder in Medusa Payment Flows
+### Using Service
-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.
+```ts
+const paymentSession =
+ await paymentModuleService.createPaymentSession(
+ paymentCollection.id,
+ {
+ provider_id: "stripe",
+ currency_code: "usd",
+ amount: 5000,
+ data: {
+ // any necessary data for the
+ // payment provider
+ },
+ }
+ )
+```
-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).
+## 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.
-# Configure Selling Products
+For example:
-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.
+### Using Step
-The concepts in this guide are applicable starting from Medusa v2.5.1.
+```ts
+import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
-## Scenario
+// ...
-Businesses can have different selling requirements:
+authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
-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.
+### Using Service
-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.
+```ts
+const payment = authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
-***
+When the payment authorization is successful, a payment is created and returned.
-## Configuring Shipping Requirements
+### Handling Additional Action
-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.
+If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
-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 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.
-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.
+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.
-### Overriding Shipping Requirements for Variants
+For example:
-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.
+```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]
-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).
+ if (updatedPaymentSession.status === "requires_more") {
+ // TODO perform required action
+ // TODO authorize payment again.
+ }
+}
+```
-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.
+## 4. Payment Flow Complete
-***
+The payment flow is complete once the payment session is authorized and the payment is created.
-## Use Case Examples
+You can then:
-By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
+- 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).
-|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.|||
+Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
# Payment
@@ -22928,208 +22916,211 @@ A payment can be refunded multiple times, and each time a refund record is creat

-# Links between Payment Module and Other Modules
+# Payment Module Provider
-This document showcases the module links defined between the Payment Module and other commerce modules.
+In this document, you’ll learn what a payment module provider is.
-## Summary
+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.
-The Payment Module has the following links to other modules:
+## What's a Payment Module Provider?
-- [`Cart` data model of Cart Module \<> `PaymentCollection` data model](#cart-module).
-- [`Customer` data model of Customer Module \<> `AccountHolder` data model](#customer-module).
-- [`Order` data model of Order Module \<> `PaymentCollection` data model](#order-module).
-- [`OrderClaim` data model of Order Module \<> `PaymentCollection` data model](#order-module).
-- [`OrderExchange` data model of Order Module \<> `PaymentCollection` data model](#order-module).
-- [`Region` data model of Region Module \<> `PaymentProvider` data model](#region-module).
+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.
-## Cart Module
+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.
-The Cart Module provides cart-related features, but not payment processing.
+### List of Payment Module Providers
-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.
+- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
-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
+## System Payment Provider
-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`:
+The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
-### query.graph
+It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
+***
-// paymentCollections.cart
-```
+## How are Payment Providers Created?
-### useQueryGraphStep
+A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+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.
-// ...
+***
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
+## Configure Payment Providers
-// paymentCollections.cart
-```
+The Payment Module accepts a `providers` option that allows you to register providers in your application.
-### Manage with Link
+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).
-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
+## PaymentProvider Data Model
-```ts
-import { Modules } from "@medusajs/framework/utils"
+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.
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-### createRemoteLinkStep
+# Payment Session
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+In this document, you’ll learn what a payment session is.
-// ...
+## What's a Payment Session?
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
+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.
-## 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`.
+## data Property
-### Retrieve with Query
+Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
-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`:
+For example, the customer's ID in Stripe is stored in the `data` property.
-### query.graph
+***
-```ts
-const { data: accountHolders } = await query.graph({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
+## Payment Session Status
-// accountHolders.customer
-```
+The `status` property of a payment session indicates its current status. Its value can be:
-### useQueryGraphStep
+- `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.
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-// ...
+# Webhook Events
-const { data: accountHolders } = useQueryGraphStep({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
+In this document, you’ll learn how the Payment Module supports listening to webhook events.
-// accountHolders.customer
-```
+## What's a Webhook Event?
-### Manage with Link
+A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+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.
-### link.create
+***
-```ts
-import { Modules } from "@medusajs/framework/utils"
+## 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.
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
+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.
+
+
+# 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.
+
+- [`Product` data model \<> `Cart` data model of Cart Module](#cart-module). (Read-only).
+- [`Product` data model \<> `ShippingProfile` data model of Fulfillment Module](#fulfillment-module).
+- [`ProductVariant` data model \<> `InventoryItem` data model of Inventory Module](#inventory-module).
+- [`Product` data model \<> `Order` data model of Order Module](#order-module). (Read-only).
+- [`ProductVariant` data model \<> `PriceSet` data model of Pricing Module](#pricing-module).
+- [`Product` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module).
+
+***
+
+## Cart Module
+
+Medusa defines read-only links between:
+
+- The `Product` 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. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model.
+- 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. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model.
+
+### Retrieve with Query
+
+To retrieve the line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `line_items.*` in `fields`:
+
+To retrieve the line items of a product, pass `line_items.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "line_items.*",
+ ],
})
+
+// variants.line_items
```
-### createRemoteLinkStep
+### useQueryGraphStep
```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "line_items.*",
+ ],
})
+
+// variants.line_items
```
***
-## Order Module
-
-An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
+## Fulfillment Module
-So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+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 order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+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: paymentCollections } = await query.graph({
- entity: "payment_collection",
+const { data: products } = await query.graph({
+ entity: "product",
fields: [
- "order.*",
+ "shipping_profile.*",
],
})
-// paymentCollections.order
+// products.shipping_profile
```
### useQueryGraphStep
@@ -23139,19 +23130,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
+const { data: products } = useQueryGraphStep({
+ entity: "product",
fields: [
- "order.*",
+ "shipping_profile.*",
],
})
-// paymentCollections.order
+// products.shipping_profile
```
### 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):
+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
@@ -23161,11 +23152,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
},
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
},
})
```
@@ -23179,40 +23170,44 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
},
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
},
})
```
***
-## Region Module
+## Inventory 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.
+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.
-This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
+
+
+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 regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
+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: paymentProviders } = await query.graph({
- entity: "payment_provider",
+const { data: variants } = await query.graph({
+ entity: "variant",
fields: [
- "regions.*",
+ "inventory_items.*",
],
})
-// paymentProviders.regions
+// variants.inventory_items
```
### useQueryGraphStep
@@ -23222,19 +23217,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: paymentProviders } = useQueryGraphStep({
- entity: "payment_provider",
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
fields: [
- "regions.*",
+ "inventory_items.*",
],
})
-// paymentProviders.regions
+// variants.inventory_items
```
### 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):
+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
@@ -23244,11 +23239,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.REGION]: {
- region_id: "reg_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
},
})
```
@@ -23262,430 +23257,406 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.REGION]: {
- region_id: "reg_123",
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
},
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
},
})
```
-
-# 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
+## Order Module
-The `providers` option is an array of payment module providers.
+Medusa defines read-only links between:
-When the Medusa application starts, these providers are registered and can be used to process payments.
+- the `Product` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model.
+- the `ProductVariant` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model.
-For example:
+### Retrieve with Query
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
+To retrieve the order line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order_items.*` in `fields`:
-// ...
+To retrieve a product's order line items, pass `order_items.*` in `fields`.
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/payment",
- options: {
- providers: [
- {
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
- options: {
- // ...
- },
- },
- ],
- },
- },
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "order_items.*",
],
})
-```
-
-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
+// variants.order_items
+```
-In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
+### useQueryGraphStep
-## What's a Payment Collection?
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-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:
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "order_items.*",
+ ],
+})
-- 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.
+// variants.order_items
+```
***
-## Multiple Payments
+## Pricing Module
-The payment collection supports multiple payment sessions and payments.
+The Product Module doesn't provide pricing-related features.
-You can use this to accept payments in increments or split payments across payment providers.
+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.
-## Usage with the Cart Module
+### Retrieve with Query
-The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
+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`:
-During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
+### query.graph
-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).
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
-
+// variants.price_set
+```
+### useQueryGraphStep
-# Accept Payment Flow
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-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.
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
-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).
+// variants.price_set
+```
-## Flow Overview
+### 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
-## 1. Create a Payment Collection
+```ts
+import { Modules } from "@medusajs/framework/utils"
-A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
+// ...
-For example:
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
-### Using Workflow
+### createRemoteLinkStep
```ts
-import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
-await createPaymentCollectionForCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: "cart_123",
- },
- })
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
```
-### Using Service
+***
-```ts
-const paymentCollection =
- await paymentModuleService.createPaymentCollections({
- currency_code: "usd",
- amount: 5000,
- })
-```
+## Sales Channel Module
-***
+The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
-## 2. Create Payment Sessions
+Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
-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.
+### Retrieve with Query
-For example:
+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`:
-### Using Workflow
+### query.graph
```ts
-import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
-const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
- .run({
- input: {
- payment_collection_id: "paycol_123",
- provider_id: "stripe",
- },
- })
+// products.sales_channels
```
-### Using Service
+### useQueryGraphStep
```ts
-const paymentSession =
- await paymentModuleService.createPaymentSession(
- paymentCollection.id,
- {
- provider_id: "stripe",
- currency_code: "usd",
- amount: 5000,
- data: {
- // any necessary data for the
- // payment provider
- },
- }
- )
-```
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-***
+// ...
-## 3. Authorize Payment Session
+const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
-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.
+// products.sales_channels
+```
-For example:
+### Manage with Link
-### Using Step
+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 { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
+import { Modules } from "@medusajs/framework/utils"
// ...
-authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
})
```
-### Using Service
+### createRemoteLinkStep
```ts
-const payment = authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
+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",
+ },
})
```
-When the payment authorization is successful, a payment is created and returned.
-### Handling Additional Action
+# Product Variant Inventory
-If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
+# Product Variant Inventory
-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 this guide, you'll learn about the inventory management features related to product variants.
-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.
+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.
-For example:
+## Configure Inventory Management of Product Variants
-```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]
+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.
- if (updatedPaymentSession.status === "requires_more") {
- // TODO perform required action
- // TODO authorize payment again.
- }
-}
-```
+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.
***
-## 4. Payment Flow Complete
+## How the Medusa Application Manages Inventory
-The payment flow is complete once the payment session is authorized and the payment is created.
+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.
-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).
+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.
-Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
+
+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).
-# Webhook Events
+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.
-In this document, you’ll learn how the Payment Module supports listening to webhook events.
+
-## What's a Webhook Event?
+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).
-A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
+### Product Inventory in Storefronts
-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.
+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.
-## 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, 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.
-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.
+***
-
+## Variant Back Orders
-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.
+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.
-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.
+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).
-### 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.
+## Additional Resources
+The following guides provide more details on inventory management in the Medusa application:
-# Payment Module Provider
+- [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).
-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.
+# Configure Selling Products
-## What's a Payment Module Provider?
+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.
-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.
+The concepts in this guide are applicable starting from Medusa v2.5.1.
-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.
+## Scenario
-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.
+Businesses can have different selling requirements:
-### List of Payment Module Providers
+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.
-- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
+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.
***
-## 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.
+## 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.
-## How are Payment Providers Created?
+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.
-A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
+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.
-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.
+### 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.
-## Configure Payment Providers
+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).
-The Payment Module accepts a `providers` option that allows you to register providers in your application.
+When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
-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).
+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.
***
-## 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.
-
+## Use Case Examples
-# Payment Session
+By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
-In this document, you’ll learn what a payment session is.
+|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.|||
-## 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.
+# Customer Accounts
-A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
+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
-## data 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.
-Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
+When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
-For example, the customer's ID in Stripe is stored in the `data` property.
+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`.
***
-## Payment Session Status
+## Email Uniqueness
-The `status` property of a payment session indicates its current status. Its value can be:
+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.
-- `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.
+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 Region Module and Other Modules
+# Links between Customer Module and Other Modules
-This document showcases the module links defined between the Region Module and other commerce modules.
+This document showcases the module links defined between the Customer Module and other commerce modules.
## Summary
-The Region Module has the following links to other modules:
+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.
-- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only)
-- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only)
-- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module).
+- [`Customer` data model \<> `AccountHolder` data model of Payment Module](#payment-module).
+- [`Cart` data model of Cart Module \<> `Customer` data model](#cart-module). (Read-only).
+- [`Order` data model of Order Module \<> `Customer` data model](#order-module). (Read-only).
***
-## Cart Module
+## Payment Module
-Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model.
+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 carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
+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: regions } = await query.graph({
- entity: "region",
+const { data: customers } = await query.graph({
+ entity: "customer",
fields: [
- "carts.*",
+ "account_holder.*",
],
})
-// regions.carts
+// customers.account_holder
```
### useQueryGraphStep
@@ -23695,81 +23666,75 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: regions } = useQueryGraphStep({
- entity: "region",
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
fields: [
- "carts.*",
+ "account_holder.*",
],
})
-// regions.carts
+// customers.account_holder
```
-***
-
-## Order Module
+### Manage with Link
-Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model.
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-### Retrieve with Query
+### link.create
-To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
+```ts
+import { Modules } from "@medusajs/framework/utils"
-### query.graph
+// ...
-```ts
-const { data: regions } = await query.graph({
- entity: "region",
- fields: [
- "orders.*",
- ],
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
})
-
-// regions.orders
```
-### useQueryGraphStep
+### createRemoteLinkStep
```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: regions } = useQueryGraphStep({
- entity: "region",
- fields: [
- "orders.*",
- ],
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
})
-
-// regions.orders
```
***
-## 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.
+## Cart Module
-
+Medusa defines a read-only link between the `Customer` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a customer's carts, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model.
### 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`:
+To retrieve a customer's carts with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
### query.graph
```ts
-const { data: regions } = await query.graph({
- entity: "region",
+const { data: customers } = await query.graph({
+ entity: "customer",
fields: [
- "payment_providers.*",
+ "carts.*",
],
})
-// regions.payment_providers
+// customers.carts
```
### useQueryGraphStep
@@ -23779,97 +23744,37 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: regions } = useQueryGraphStep({
- entity: "region",
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
fields: [
- "payment_providers.*",
+ "carts.*",
],
})
-// 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",
- },
-})
+// customers.carts
```
-
-# Links between Sales Channel Module and Other Modules
-
-This document showcases the module links defined between the Sales Channel Module and other commerce modules.
-
-## Summary
-
-The Sales Channel 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.
-
-- [`ApiKey` data model of the API Key Module \<> `SalesChannel` data model](#api-key-module).
-- [`SalesChannel` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only)
-- [`SalesChannel` data model \<> `Order` data model of the Order Module](#order-module). (Read-only)
-- [`Product` data model of the Product Module \<> `SalesChannel` data model](#product-module).
-- [`SalesChannel` data model \<> `StockLocation` data model of the Stock Location Module](#stock-location-module).
-
***
-## API Key Module
-
-A publishable API key allows you to easily specify the sales channel scope in a client request.
-
-Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
+## Order Module
-
+Medusa defines a read-only link between the `Customer` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a customer's orders, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model.
### Retrieve with Query
-To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`:
+To retrieve a customer's orders with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
### query.graph
```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
+const { data: customers } = await query.graph({
+ entity: "customer",
fields: [
- "publishable_api_keys.*",
+ "orders.*",
],
})
-// salesChannels.publishable_api_keys
+// customers.orders
```
### useQueryGraphStep
@@ -23879,76 +23784,52 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
fields: [
- "publishable_api_keys.*",
+ "orders.*",
],
})
-// salesChannels.publishable_api_keys
+// customers.orders
```
-### 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"
-// ...
+# Links between Region Module and Other Modules
-await link.create({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
+This document showcases the module links defined between the Region Module and other commerce modules.
-### createRemoteLinkStep
+## Summary
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+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.
-createRemoteLinkStep({
- [Modules.API_KEY]: {
- api_key_id: "apk_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
+- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only)
+- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only)
+- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module).
***
## Cart Module
-Medusa defines a read-only link between the `SalesChannel` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a sales channel's carts, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model.
+Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model.
### Retrieve with Query
-To retrieve the carts of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
+To retrieve the carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
### query.graph
```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
+const { data: regions } = await query.graph({
+ entity: "region",
fields: [
"carts.*",
],
})
-// salesChannels.carts
+// regions.carts
```
### useQueryGraphStep
@@ -23958,37 +23839,37 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
+const { data: regions } = useQueryGraphStep({
+ entity: "region",
fields: [
"carts.*",
],
})
-// salesChannels.carts
+// regions.carts
```
***
## Order Module
-Medusa defines a read-only link between the `SalesChannel` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a sales channel's orders, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model.
+Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model.
### Retrieve with Query
-To retrieve the orders of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
+To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
### query.graph
```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
+const { data: regions } = await query.graph({
+ entity: "region",
fields: [
"orders.*",
],
})
-// salesChannels.orders
+// regions.orders
```
### useQueryGraphStep
@@ -23998,41 +23879,41 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
+const { data: regions } = useQueryGraphStep({
+ entity: "region",
fields: [
"orders.*",
],
})
-// salesChannels.orders
+// regions.orders
```
***
-## Product Module
+## Payment Module
-A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models.
+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.
-A product can be available in more than one sales channel. You can retrieve only the products of a sales channel.
+
### Retrieve with Query
-To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
+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: salesChannels } = await query.graph({
- entity: "sales_channel",
+const { data: regions } = await query.graph({
+ entity: "region",
fields: [
- "products.*",
+ "payment_providers.*",
],
})
-// salesChannels.products
+// regions.payment_providers
```
### useQueryGraphStep
@@ -24042,19 +23923,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
+const { data: regions } = useQueryGraphStep({
+ entity: "region",
fields: [
- "products.*",
+ "payment_providers.*",
],
})
-// salesChannels.products
+// regions.payment_providers
```
### 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):
+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
@@ -24064,11 +23945,11 @@ import { Modules } from "@medusajs/framework/utils"
// ...
await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.REGION]: {
+ region_id: "reg_123",
},
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
},
})
```
@@ -24082,387 +23963,72 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
+ [Modules.REGION]: {
+ region_id: "reg_123",
},
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
},
})
```
-***
-
-## Stock Location Module
-
-A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel.
-
-Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-
+# Application Method
-### Retrieve with Query
+In this document, you'll learn what an application method is.
-To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
+## What is an Application Method?
-### query.graph
+The [ApplicationMethod data model](https://docs.medusajs.com/references/promotion/models/ApplicationMethod/index.html.md) defines how a promotion is applied:
-```ts
-const { data: salesChannels } = await query.graph({
- entity: "sales_channel",
- fields: [
- "stock_locations.*",
- ],
-})
+|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?|
-// salesChannels.stock_locations
-```
+## Target Promotion Rules
-### useQueryGraphStep
+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.
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+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.
-// ...
+
-const { data: salesChannels } = useQueryGraphStep({
- entity: "sales_channel",
- fields: [
- "stock_locations.*",
- ],
-})
+In this example, the promotion is only applied on products in the cart having the SKU `SHIRT`.
-// salesChannels.stock_locations
-```
+***
-### Manage with Link
+## Buy Promotion Rules
-To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+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.
-### link.create
+The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation.
-```ts
-import { Modules } from "@medusajs/framework/utils"
+
-// ...
+In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied.
-await link.create({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
-### createRemoteLinkStep
+# Promotion Actions
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+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
-createRemoteLinkStep({
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
- [Modules.STOCK_LOCATION]: {
- sales_channel_id: "sloc_123",
- },
-})
-```
+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.
-# Publishable API Keys with Sales Channels
+***
-In this document, you’ll learn what publishable API keys are and how to use them with sales channels.
+## Action Types
-## Publishable API Keys with Sales Channels
+### `addItemAdjustment` Action
-A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels.
+The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
-When sending a request to a Store API route, you must pass a publishable API key in the header of the request:
-
-```bash
-curl http://localhost:9000/store/products \
- x-publishable-api-key: {your_publishable_api_key}
-```
-
-The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used.
-
-***
-
-## How to Create a Publishable API Key?
-
-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).
-
-
-# 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.
-
-- [`FulfillmentSet` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module).
-- [`FulfillmentProvider` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module).
-- [`StockLocation` data model \<> `Inventory` data model of the Inventory Module](#inventory-module).
-- [`SalesChannel` data model of the Sales Channel Module \<> `StockLocation` data model](#sales-channel-module).
-
-***
-
-## 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 `StockLocation` data model and the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model. This means you can retrieve the details of a stock location's inventory levels, 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 inventory levels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_levels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "inventory_levels.*",
- ],
-})
-
-// stockLocations.inventory_levels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "inventory_levels.*",
- ],
-})
-
-// stockLocations.inventory_levels
-```
-
-***
-
-## 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",
- },
-})
-```
-
-
-# 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:
+This action has the following format:
```ts
export interface AddItemAdjustmentAction {
@@ -24555,43 +24121,6 @@ export interface CampaignBudgetExceededAction {
Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
-# 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.
@@ -24856,41 +24385,71 @@ createRemoteLinkStep({
```
-# Links between Store Module and Other Modules
+# Publishable API Keys with Sales Channels
-This document showcases the module links defined between the Store Module and other commerce modules.
+In this document, you’ll learn what publishable API keys are and how to use them with sales channels.
+
+## Publishable API Keys with Sales Channels
+
+A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels.
+
+When sending a request to a Store API route, you must pass a publishable API key in the header of the request:
+
+```bash
+curl http://localhost:9000/store/products \
+ x-publishable-api-key: {your_publishable_api_key}
+```
+
+The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used.
+
+***
+
+## How to Create a Publishable API Key?
+
+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).
+
+
+# Links between Sales Channel Module and Other Modules
+
+This document showcases the module links defined between the Sales Channel Module and other commerce modules.
## Summary
-The Store Module has the following links to other modules:
+The Sales Channel 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.
-- [`Currency` data model \<> `Currency` data model of Currency Module](#currency-module). (Read-only).
+- [`ApiKey` data model of the API Key Module \<> `SalesChannel` data model](#api-key-module).
+- [`SalesChannel` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only)
+- [`SalesChannel` data model \<> `Order` data model of the Order Module](#order-module). (Read-only)
+- [`Product` data model of the Product Module \<> `SalesChannel` data model](#product-module).
+- [`SalesChannel` data model \<> `StockLocation` data model of the Stock Location Module](#stock-location-module).
***
-## Currency Module
+## API Key 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.
+A publishable API key allows you to easily specify the sales channel scope in a client request.
-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` data model in the Store Module.
+Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
+
+
### 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`:
+To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`:
### query.graph
```ts
-const { data: stores } = await query.graph({
- entity: "store",
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
fields: [
- "supported_currencies.currency.*",
+ "publishable_api_keys.*",
],
})
-// stores.supported_currencies
+// salesChannels.publishable_api_keys
```
### useQueryGraphStep
@@ -24900,477 +24459,555 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: stores } = useQueryGraphStep({
- entity: "store",
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
fields: [
- "supported_currencies.currency.*",
+ "publishable_api_keys.*",
],
})
-// stores.supported_currencies
+// salesChannels.publishable_api_keys
```
+### Manage with Link
-# Tax Module Options
+To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-In this document, you'll learn about the options of the Tax Module.
+### link.create
-## providers
+```ts
+import { Modules } from "@medusajs/framework/utils"
-The `providers` option is an array of either tax module providers or path to a file that defines a tax provider.
+// ...
-When the Medusa application starts, these providers are registered and can be used to retrieve tax lines.
+await link.create({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
-```ts title="medusa-config.ts"
+### createRemoteLinkStep
+
+```ts
import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
// ...
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/tax",
- options: {
- providers: [
- {
- resolve: "./path/to/my-provider",
- id: "my-provider",
- options: {
- // ...
- },
- },
- ],
- },
- },
- ],
+createRemoteLinkStep({
+ [Modules.API_KEY]: {
+ api_key_id: "apk_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
})
```
-The objects in the array accept the following properties:
+***
-- `resolve`: A string indicating the package name of the module provider or the path to it.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
+## Cart Module
+Medusa defines a read-only link between the `SalesChannel` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a sales channel's carts, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model.
-# Tax Rates and Rules
+### Retrieve with Query
-In this document, you’ll learn about tax rates and rules.
+To retrieve the carts of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
-Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions#manage-tax-rate-overrides/index.html.md) to learn how to manage tax rates using the dashboard.
+### query.graph
-## What are Tax Rates?
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "carts.*",
+ ],
+})
-A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total.
+// salesChannels.carts
+```
-Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region.
+### useQueryGraphStep
-### Combinable Tax Rates
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`.
+// ...
-Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned.
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// salesChannels.carts
+```
***
-## Override Tax Rates with Rules
+## Order Module
-You can create tax rates that override the default for specific conditions or rules.
+Medusa defines a read-only link between the `SalesChannel` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a sales channel's orders, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model.
-For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15.
+### Retrieve with Query
-A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule.
+To retrieve the orders of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
-
+### query.graph
-These two properties of the data model identify the rule’s target:
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "orders.*",
+ ],
+})
-- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
-- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
+// salesChannels.orders
+```
-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.
+### useQueryGraphStep
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-# Tax Region
+// ...
-In this document, you’ll learn about tax regions and how to use them with the Region Module.
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "orders.*",
+ ],
+})
-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.
+// salesChannels.orders
+```
-## 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.
+## Product Module
-Tax regions can inherit settings and rules from a parent tax region.
+A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models.
-Each tax region has tax rules and a tax provider.
+
+A product can be available in more than one sales channel. You can retrieve only the products of a sales channel.
-# Tax Calculation with the Tax Provider
+### Retrieve with Query
-In this document, you’ll learn how tax lines are calculated and what a tax provider is.
+To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
-## Tax Lines Calculation
+### query.graph
-Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation.
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "products.*",
+ ],
+})
-For example:
+// salesChannels.products
+```
+
+### useQueryGraphStep
```ts
-const taxLines = await taxModuleService.getTaxLines(
- [
- {
- id: "cali_123",
- product_id: "prod_123",
- unit_price: 1000,
- quantity: 1,
- },
- {
- id: "casm_123",
- shipping_option_id: "so_123",
- unit_price: 2000,
- },
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "products.*",
],
- {
- address: {
- country_code: "us",
- },
- }
-)
+})
+
+// salesChannels.products
```
-The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer.
+### Manage with Link
-The example above retrieves the tax lines based on the tax region for the United States.
+To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-The method returns tax lines for the line item and shipping methods. For example:
+### link.create
-```json
-[
- {
- "line_item_id": "cali_123",
- "rate_id": "txr_1",
- "rate": 10,
- "code": "XXX",
- "name": "Tax Rate 1"
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
},
- {
- "shipping_line_id": "casm_123",
- "rate_id": "txr_2",
- "rate": 5,
- "code": "YYY",
- "name": "Tax Rate 2"
- }
-]
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
```
-***
+### createRemoteLinkStep
-## Using the Tax Provider in the Calculation
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider.
+// ...
-A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider.
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
-The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type.
+***
-{/* ---
+## Stock Location Module
-TODO add once tax provider guide is updated + add module providers match other modules.
+A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel.
-## Create Tax Provider
+Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
+
+### Retrieve with Query
-# User Creation Flows
+To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-In this document, learn the different ways to create a user using the User Module.
+### query.graph
-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.
+```ts
+const { data: salesChannels } = await query.graph({
+ entity: "sales_channel",
+ fields: [
+ "stock_locations.*",
+ ],
+})
-## Straightforward User Creation
+// salesChannels.stock_locations
+```
-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):
+### useQueryGraphStep
```ts
-const user = await userModuleService.createUsers({
- email: "user@example.com",
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: salesChannels } = useQueryGraphStep({
+ entity: "sales_channel",
+ fields: [
+ "stock_locations.*",
+ ],
})
-```
-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).
+// salesChannels.stock_locations
+```
-***
+### Manage with Link
-## Invite Users
+To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-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:
+### link.create
```ts
-const invite = await userModuleService.createInvites({
- email: "user@example.com",
+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",
+ },
})
```
-Later, you can accept the invite and create a new user for them:
+### createRemoteLinkStep
```ts
-const invite =
- await userModuleService.validateInviteToken("secret_123")
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-await userModuleService.updateInvites({
- id: invite.id,
- accepted: true,
-})
+// ...
-const user = await userModuleService.createUsers({
- email: invite.email,
+createRemoteLinkStep({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
})
```
-### 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):
+# Links between Stock Location Module and Other Modules
-```ts
-await userModuleService.refreshInviteTokens(["invite_123"])
-```
+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.
+
+- [`FulfillmentSet` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module).
+- [`FulfillmentProvider` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module).
+- [`StockLocation` data model \<> `Inventory` data model of the Inventory Module](#inventory-module).
+- [`SalesChannel` data model of the Sales Channel Module \<> `StockLocation` data model](#sales-channel-module).
***
-## Create Identity with the Auth Module
+## Fulfillment 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.
+A fulfillment set can be conditioned to a specific stock location.
-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:
+Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
-```ts
-const { success, authIdentity } =
- await authModuleService.authenticate("emailpass", {
- // ...
- })
+
-const [, count] = await userModuleService.listAndCountUsers({
- email: authIdentity.entity_id,
-})
+Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
-if (!count) {
- const user = await userModuleService.createUsers({
- email: authIdentity.entity_id,
- })
-}
-```
+
+### Retrieve with Query
-# User Module Options
+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`:
-In this document, you'll learn about the options of the User Module.
+To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`.
-## Module Options
+### query.graph
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
+```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"
// ...
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/user",
- options: {
- jwt_secret: process.env.JWT_SECRET,
- },
- },
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "fulfillment_sets.*",
],
})
+
+// stockLocations.fulfillment_sets
```
-|Option|Description|Required|
-|---|---|---|---|---|
-|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes|
+### Manage with Link
-### Environment Variables
+To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-Make sure to add the necessary environment variables for the above options in `.env`:
+### link.create
-```bash
-JWT_SECRET=supersecret
+```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
-# Emailpass Auth Module Provider
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
+// ...
-Using the Emailpass auth module provider, you allow users to register and login with an email and password.
+createRemoteLinkStep({
+ [Modules.STOCK_LOCATION]: {
+ stock_location_id: "sloc_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_set_id: "fset_123",
+ },
+})
+```
***
-## Register the Emailpass Auth Module Provider
+## Inventory Module
-The Emailpass auth provider is registered by default with the Auth Module.
+Medusa defines a read-only link between the `StockLocation` data model and the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model. This means you can retrieve the details of a stock location's inventory levels, 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.
-If you want to pass options to the provider, add the provider to the `providers` option of the Auth Module:
+### Retrieve with Query
-```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+To retrieve the inventory levels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_levels.*` in `fields`:
-// ...
+### query.graph
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
- options: {
- providers: [
- // other providers...
- {
- resolve: "@medusajs/medusa/auth-emailpass",
- id: "emailpass",
- options: {
- // options...
- },
- },
- ],
- },
- },
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "inventory_levels.*",
],
})
+
+// stockLocations.inventory_levels
```
-### Module Options
+### useQueryGraphStep
-|Configuration|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`hashConfig\`|An object of configurations to use when hashing the user's
-password. Refer to |No|\`\`\`ts
-const hashConfig = \{
- logN: 15,
- r: 8,
- p: 1
-}
-\`\`\`|
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-***
+// ...
-## Related Guides
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "inventory_levels.*",
+ ],
+})
-- [How to register a customer using email and password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md)
+// stockLocations.inventory_levels
+```
+***
-# Google Auth Module Provider
+## Sales Channel Module
-In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
+A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
-The Google Auth Module Provider authenticates users with their Google account.
+Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
-Learn about the authentication flow for third-party providers in [this guide](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).
+
-***
+### Retrieve with Query
-## Register the Google Auth Module Provider
+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`:
-### Prerequisites
+### query.graph
-- [Create a project in Google Cloud.](https://cloud.google.com/resource-manager/docs/creating-managing-projects)
-- [Create authorization credentials. When setting the Redirect Uri, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred)
+```ts
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
+ ],
+})
-Add the module to the array of providers passed to the Auth Module:
+// stockLocations.sales_channels
+```
-```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-module.exports = defineConfig({
- // ...
- modules: [
- {
- // ...
- [Modules.AUTH]: {
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
- options: {
- providers: [
- // other providers...
- {
- resolve: "@medusajs/medusa/auth-google",
- id: "google",
- options: {
- clientId: process.env.GOOGLE_CLIENT_ID,
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
- callbackUrl: process.env.GOOGLE_CALLBACK_URL,
- },
- },
- ],
- },
- },
- },
+const { data: stockLocations } = useQueryGraphStep({
+ entity: "stock_location",
+ fields: [
+ "sales_channels.*",
],
})
+
+// stockLocations.sales_channels
```
-### Environment Variables
+### Manage with Link
-Make sure to add the necessary environment variables for the above options in `.env`:
+To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-```plain
-GOOGLE_CLIENT_ID=
-GOOGLE_CLIENT_SECRET=
-GOOGLE_CALLBACK_URL=
-```
+### link.create
-### Module Options
+```ts
+import { Modules } from "@medusajs/framework/utils"
-|Configuration|Description|Required|
-|---|---|---|---|---|
-|\`clientId\`|A string indicating the |Yes|
-|\`clientSecret\`|A string indicating the |Yes|
-|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in Google.|Yes|
+// ...
-***
+await link.create({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
-***
+### createRemoteLinkStep
-## Override Callback URL During Authentication
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-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).
+createRemoteLinkStep({
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+ [Modules.STOCK_LOCATION]: {
+ sales_channel_id: "sloc_123",
+ },
+})
+```
-***
-## Examples
+# Stock Location Concepts
-- [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).
+In this document, you’ll learn about the main concepts in the Stock Location Module.
+## Stock Location
-# GitHub Auth Module Provider
+A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
-In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
+Medusa uses stock locations to provide inventory details, from the Inventory Module, per location.
-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).
+## 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.
-## Register the Github Auth Module Provider
-### Prerequisites
+# User Module Options
-- [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)
+In this document, you'll learn about the options of the User Module.
-Add the module to the array of providers passed to the Auth Module:
+## Module Options
```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
+import { Modules } from "@medusajs/framework/utils"
// ...
@@ -25378,343 +25015,352 @@ module.exports = defineConfig({
// ...
modules: [
{
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
+ resolve: "@medusajs/user",
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,
- },
- },
- ],
+ 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`:
-```plain
-GITHUB_CLIENT_ID=
-GITHUB_CLIENT_SECRET=
-GITHUB_CALLBACK_URL=
+```bash
+JWT_SECRET=supersecret
```
-### 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
+# User Creation Flows
-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.
+In this document, learn the different ways to create a user using the User Module.
-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 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
-## Examples
+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):
-- [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).
+```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).
-# Get Product Variant Prices using Query
+***
-In this document, you'll learn how to retrieve product variant prices in the Medusa application using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
+## Invite Users
-The Product Module doesn't provide pricing functionalities. The Medusa application links the Product Module's `ProductVariant` data model to the Pricing Module's `PriceSet` data model.
+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:
-So, to retrieve data across the linked records of the two modules, you use Query.
+```ts
+const invite = await userModuleService.createInvites({
+ email: "user@example.com",
+})
+```
-## Retrieve All Product Variant Prices
+Later, you can accept the invite and create a new user for them:
-To retrieve all product variant prices, retrieve the product using Query and include among its fields `variants.prices.*`.
+```ts
+const invite =
+ await userModuleService.validateInviteToken("secret_123")
-For example:
+await userModuleService.updateInvites({
+ id: invite.id,
+ accepted: true,
+})
-```ts highlights={[["6"]]}
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "*",
- "variants.*",
- "variants.prices.*",
- ],
- filters: {
- id: [
- "prod_123",
- ],
- },
+const user = await userModuleService.createUsers({
+ email: invite.email,
})
```
-Each variant in the retrieved products has a `prices` array property with all the product variant prices. Each price object has the properties of the [Pricing Module's Price data model](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
+### 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"])
+```
***
-## Retrieve Calculated Price for a Context
+## Create Identity with the Auth Module
-The Pricing Module can calculate prices of a variant based on a [context](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), such as the region ID or the currency code.
+By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users.
-Learn more about prices calculation in [this Pricing Module documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md).
+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:
-To retrieve calculated prices of variants based on a context, retrieve the products using Query and:
+```ts
+const { success, authIdentity } =
+ await authModuleService.authenticate("emailpass", {
+ // ...
+ })
-- Pass `variants.calculated_price.*` in the `fields` property.
-- Pass a `context` property in the object parameter. Its value is an object of objects that sets the context for the retrieved fields.
+const [, count] = await userModuleService.listAndCountUsers({
+ email: authIdentity.entity_id,
+})
-For example:
+if (!count) {
+ const user = await userModuleService.createUsers({
+ email: authIdentity.entity_id,
+ })
+}
+```
-```ts highlights={[["10"], ["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"]]}
-import { QueryContext } from "@medusajs/framework/utils"
+
+# Tax Module Options
+
+In this document, you'll learn about the options of the Tax Module.
+
+## providers
+
+The `providers` option is an array of either tax module providers or path to a file that defines a tax provider.
+
+When the Medusa application starts, these providers are registered and can be used to retrieve tax lines.
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
// ...
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "*",
- "variants.*",
- "variants.calculated_price.*",
- ],
- filters: {
- id: "prod_123",
- },
- context: {
- variants: {
- calculated_price: QueryContext({
- region_id: "reg_01J3MRPDNXXXDSCC76Y6YCZARS",
- currency_code: "eur",
- }),
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/tax",
+ options: {
+ providers: [
+ {
+ resolve: "./path/to/my-provider",
+ id: "my-provider",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
},
- },
+ ],
})
```
-For the context of the product variant's calculated price, you pass an object to `context` with the property `variants`, whose value is another object with the property `calculated_price`.
+The objects in the array accept the following properties:
-`calculated_price`'s value is created using `QueryContext` from the Modules SDK, passing it a [calculation context object](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md).
+- `resolve`: A string indicating the package name of the module provider or the path to it.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
-Each variant in the retrieved products has a `calculated_price` object. Learn more about its properties in [this Pricing Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
+# Tax Rates and Rules
-# Calculate Product Variant Price with Taxes
+In this document, you’ll learn about tax rates and rules.
-In this document, you'll learn how to calculate a product variant's price with taxes.
+Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/tax-regions#manage-tax-rate-overrides/index.html.md) to learn how to manage tax rates using the dashboard.
-## Step 0: Resolve Resources
+## What are Tax Rates?
-You'll need the following resources for the taxes calculation:
+A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total.
-1. [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to retrieve the product's variants' prices for a context. Learn more about that in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md).
-2. The Tax Module's main service to get the tax lines for each product.
+Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region.
-```ts
-// other imports...
-import {
- Modules,
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
+### Combinable Tax Rates
-// In an API route, workflow step, etc...
-const query = container.resolve(ContainerRegistrationKeys.QUERY)
-const taxModuleService = container.resolve(
- Modules.TAX
-)
-```
+Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`.
+
+Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned.
***
-## Step 1: Retrieve Prices for a Context
+## Override Tax Rates with Rules
-After resolving the resources, use Query to retrieve the products with the variants' prices for a context:
+You can create tax rates that override the default for specific conditions or rules.
-Learn more about retrieving product variants' prices for a context in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md).
+For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15.
-```ts
-import { QueryContext } from "@medusajs/framework/utils"
+A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule.
-// ...
+
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "*",
- "variants.*",
- "variants.calculated_price.*",
- ],
- filters: {
- id: "prod_123",
- },
- context: {
- variants: {
- calculated_price: QueryContext({
- region_id: "region_123",
- currency_code: "usd",
- }),
- },
- },
-})
-```
+These two properties of the data model identify the rule’s target:
-***
+- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
+- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
-## Step 2: Get Tax Lines for Products
+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.
-To retrieve the tax line of each product, first, add the following utility method:
-```ts
-// other imports...
-import {
- HttpTypes,
- TaxableItemDTO,
-} from "@medusajs/framework/types"
+# Tax Region
-// ...
-const asTaxItem = (product: HttpTypes.StoreProduct): TaxableItemDTO[] => {
- return product.variants
- ?.map((variant) => {
- if (!variant.calculated_price) {
- return
- }
+In this document, you’ll learn about tax regions and how to use them with the Region Module.
- return {
- id: variant.id,
- product_id: product.id,
- product_name: product.title,
- product_categories: product.categories?.map((c) => c.name),
- product_category_id: product.categories?.[0]?.id,
- product_sku: variant.sku,
- product_type: product.type,
- product_type_id: product.type_id,
- quantity: 1,
- unit_price: variant.calculated_price.calculated_amount,
- currency_code: variant.calculated_price.currency_code,
- }
- })
- .filter((v) => !!v) as unknown as TaxableItemDTO[]
-}
-```
+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.
-This formats the products as items to calculate tax lines for.
+## What is a Tax Region?
-Then, use it when retrieving the tax lines of the products retrieved earlier:
+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.
-```ts
-// other imports...
-import {
- ItemTaxLineDTO,
-} from "@medusajs/framework/types"
+Tax regions can inherit settings and rules from a parent tax region.
-// ...
-const taxLines = (await taxModuleService.getTaxLines(
- products.map(asTaxItem).flat(),
+Each tax region has tax rules and a tax provider.
+
+
+# Tax Calculation with the Tax Provider
+
+In this document, you’ll learn how tax lines are calculated and what a tax provider is.
+
+## Tax Lines Calculation
+
+Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation.
+
+For example:
+
+```ts
+const taxLines = await taxModuleService.getTaxLines(
+ [
+ {
+ id: "cali_123",
+ product_id: "prod_123",
+ unit_price: 1000,
+ quantity: 1,
+ },
+ {
+ id: "casm_123",
+ shipping_option_id: "so_123",
+ unit_price: 2000,
+ },
+ ],
{
- // example of context properties. You can pass other ones.
address: {
- country_code,
+ country_code: "us",
},
}
-)) as unknown as ItemTaxLineDTO[]
+)
```
-You use the Tax Module's main service's [getTaxLines method](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md) to retrieve the tax line.
+The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer.
-For the first parameter, you use the `asTaxItem` function to format the products as expected by the `getTaxLines` method.
+The example above retrieves the tax lines based on the tax region for the United States.
-For the second parameter, you pass the current context. You can pass other details such as the customer's ID.
+The method returns tax lines for the line item and shipping methods. For example:
-Learn about the other context properties to pass in [the getTaxLines method's reference](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md).
+```json
+[
+ {
+ "line_item_id": "cali_123",
+ "rate_id": "txr_1",
+ "rate": 10,
+ "code": "XXX",
+ "name": "Tax Rate 1"
+ },
+ {
+ "shipping_line_id": "casm_123",
+ "rate_id": "txr_2",
+ "rate": 5,
+ "code": "YYY",
+ "name": "Tax Rate 2"
+ }
+]
+```
***
-## Step 3: Calculate Price with Tax for Variant
+## Using the Tax Provider in the Calculation
-To calculate the price with and without taxes for a variant, first, group the tax lines retrieved in the previous step by variant IDs:
+The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider.
-```ts highlights={taxLineHighlights}
-const taxLinesMap = new Map()
-taxLines.forEach((taxLine) => {
- const variantId = taxLine.line_item_id
- if (!taxLinesMap.has(variantId)) {
- taxLinesMap.set(variantId, [])
- }
+A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider.
- taxLinesMap.get(variantId)?.push(taxLine)
-})
-```
+The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type.
-Notice that the variant's ID is stored in the `line_item_id` property of a tax line since tax lines are used for line items in a cart.
+{/* ---
-Then, loop over the products and their variants to retrieve the prices with and without taxes:
+TODO add once tax provider guide is updated + add module providers match other modules.
-```ts highlights={calculateTaxHighlights}
-// other imports...
-import {
- calculateAmountsWithTax,
-} from "@medusajs/framework/utils"
+## Create Tax Provider
-// ...
-products.forEach((product) => {
- product.variants?.forEach((variant) => {
- if (!variant.calculated_price) {
- return
- }
+Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
- const taxLinesForVariant = taxLinesMap.get(variant.id) || []
- const { priceWithTax, priceWithoutTax } = calculateAmountsWithTax({
- taxLines: taxLinesForVariant,
- amount: variant.calculated_price!.calculated_amount!,
- includesTax:
- variant.calculated_price!.is_calculated_price_tax_inclusive!,
- })
- // do something with prices...
- })
+# 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.
+
+- [`Currency` data model \<> `Currency` data model of Currency Module](#currency-module). (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` data model in the Store 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
```
-For each product variant, you:
+### useQueryGraphStep
-1. Retrieve its tax lines from the `taxLinesMap`.
-2. Calculate its prices with and without taxes using the `calculateAmountsWithTax` from the Medusa Framework.
-3. The `calculateAmountsWithTax` function returns an object having two properties:
- - `priceWithTax`: The variant's price with the taxes applied.
- - `priceWithoutTax`: The variant's price without taxes applied.
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+// ...
-# Stripe Module Provider
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
-In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module.
+// stores.supported_currencies
+```
-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
+# Emailpass Auth Module Provider
-### Prerequisites
+In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
-- [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)
+Using the Emailpass auth module provider, you allow users to register and login with an email and password.
-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`:
+***
+
+## Register the Emailpass Auth Module Provider
+
+The Emailpass auth provider is registered by default with the Auth Module.
+
+If you want to pass options to the provider, add the provider to the `providers` option of the Auth Module:
```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
+import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
// ...
@@ -25722,14 +25368,16 @@ module.exports = defineConfig({
// ...
modules: [
{
- resolve: "@medusajs/medusa/payment",
+ resolve: "@medusajs/medusa/auth",
+ dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
options: {
providers: [
+ // other providers...
{
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
+ resolve: "@medusajs/medusa/auth-emailpass",
+ id: "emailpass",
options: {
- apiKey: process.env.STRIPE_API_KEY,
+ // options...
},
},
],
@@ -25739,49 +25387,280 @@ module.exports = defineConfig({
})
```
-### 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|
+|Configuration|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|-|
+|\`hashConfig\`|An object of configurations to use when hashing the user's
+password. Refer to |No|\`\`\`ts
+const hashConfig = \{
+ logN: 15,
+ r: 8,
+ p: 1
+}
+\`\`\`|
***
-## Enable Stripe Providers in a Region
+## Related Guides
-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.
+- [How to register a customer using email and password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/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.
-***
+# GitHub Auth Module Provider
-## Setup Stripe Webhooks
+In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
-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.
+The Github Auth Module Provider authenticates users with their GitHub account.
-### Webhook URL
+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).
-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.
+## Register the Github Auth Module Provider
-The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each:
+### Prerequisites
-|Stripe Payment Type|Webhook Endpoint URL|
-|---|---|---|
+- [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).
+
+
+# Google Auth Module Provider
+
+In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
+
+The Google Auth Module Provider authenticates users with their Google account.
+
+Learn about the authentication flow for third-party providers in [this guide](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).
+
+***
+
+## Register the Google Auth Module Provider
+
+### Prerequisites
+
+- [Create a project in Google Cloud.](https://cloud.google.com/resource-manager/docs/creating-managing-projects)
+- [Create authorization credentials. When setting the Redirect Uri, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred)
+
+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: [
+ {
+ // ...
+ [Modules.AUTH]: {
+ resolve: "@medusajs/medusa/auth",
+ dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
+ options: {
+ providers: [
+ // other providers...
+ {
+ resolve: "@medusajs/medusa/auth-google",
+ id: "google",
+ options: {
+ clientId: process.env.GOOGLE_CLIENT_ID,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
+ callbackUrl: process.env.GOOGLE_CALLBACK_URL,
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+})
+```
+
+### Environment Variables
+
+Make sure to add the necessary environment variables for the above options in `.env`:
+
+```plain
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+GOOGLE_CALLBACK_URL=
+```
+
+### Module Options
+
+|Configuration|Description|Required|
+|---|---|---|---|---|
+|\`clientId\`|A string indicating the |Yes|
+|\`clientSecret\`|A string indicating the |Yes|
+|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in Google.|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 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).
+
+
+# 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\`|
@@ -25807,328 +25686,593 @@ When you set up the webhook in Stripe, choose the following events to listen to:
- [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).
+# Calculate Product Variant Price with Taxes
+
+In this document, you'll learn how to calculate a product variant's price with taxes.
+
+## Step 0: Resolve Resources
+
+You'll need the following resources for the taxes calculation:
+
+1. [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to retrieve the product's variants' prices for a context. Learn more about that in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md).
+2. The Tax Module's main service to get the tax lines for each product.
+
+```ts
+// other imports...
+import {
+ Modules,
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+// In an API route, workflow step, etc...
+const query = container.resolve(ContainerRegistrationKeys.QUERY)
+const taxModuleService = container.resolve(
+ Modules.TAX
+)
+```
+
+***
+
+## Step 1: Retrieve Prices for a Context
+
+After resolving the resources, use Query to retrieve the products with the variants' prices for a context:
+
+Learn more about retrieving product variants' prices for a context in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md).
+
+```ts
+import { QueryContext } from "@medusajs/framework/utils"
+
+// ...
+
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "*",
+ "variants.*",
+ "variants.calculated_price.*",
+ ],
+ filters: {
+ id: "prod_123",
+ },
+ context: {
+ variants: {
+ calculated_price: QueryContext({
+ region_id: "region_123",
+ currency_code: "usd",
+ }),
+ },
+ },
+})
+```
+
+***
+
+## Step 2: Get Tax Lines for Products
+
+To retrieve the tax line of each product, first, add the following utility method:
+
+```ts
+// other imports...
+import {
+ HttpTypes,
+ TaxableItemDTO,
+} from "@medusajs/framework/types"
+
+// ...
+const asTaxItem = (product: HttpTypes.StoreProduct): TaxableItemDTO[] => {
+ return product.variants
+ ?.map((variant) => {
+ if (!variant.calculated_price) {
+ return
+ }
+
+ return {
+ id: variant.id,
+ product_id: product.id,
+ product_name: product.title,
+ product_categories: product.categories?.map((c) => c.name),
+ product_category_id: product.categories?.[0]?.id,
+ product_sku: variant.sku,
+ product_type: product.type,
+ product_type_id: product.type_id,
+ quantity: 1,
+ unit_price: variant.calculated_price.calculated_amount,
+ currency_code: variant.calculated_price.currency_code,
+ }
+ })
+ .filter((v) => !!v) as unknown as TaxableItemDTO[]
+}
+```
+
+This formats the products as items to calculate tax lines for.
+
+Then, use it when retrieving the tax lines of the products retrieved earlier:
+
+```ts
+// other imports...
+import {
+ ItemTaxLineDTO,
+} from "@medusajs/framework/types"
+
+// ...
+const taxLines = (await taxModuleService.getTaxLines(
+ products.map(asTaxItem).flat(),
+ {
+ // example of context properties. You can pass other ones.
+ address: {
+ country_code,
+ },
+ }
+)) as unknown as ItemTaxLineDTO[]
+```
+
+You use the Tax Module's main service's [getTaxLines method](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md) to retrieve the tax line.
+
+For the first parameter, you use the `asTaxItem` function to format the products as expected by the `getTaxLines` method.
+
+For the second parameter, you pass the current context. You can pass other details such as the customer's ID.
+
+Learn about the other context properties to pass in [the getTaxLines method's reference](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md).
+
+***
+
+## Step 3: Calculate Price with Tax for Variant
+
+To calculate the price with and without taxes for a variant, first, group the tax lines retrieved in the previous step by variant IDs:
+
+```ts highlights={taxLineHighlights}
+const taxLinesMap = new Map()
+taxLines.forEach((taxLine) => {
+ const variantId = taxLine.line_item_id
+ if (!taxLinesMap.has(variantId)) {
+ taxLinesMap.set(variantId, [])
+ }
+
+ taxLinesMap.get(variantId)?.push(taxLine)
+})
+```
+
+Notice that the variant's ID is stored in the `line_item_id` property of a tax line since tax lines are used for line items in a cart.
+
+Then, loop over the products and their variants to retrieve the prices with and without taxes:
+
+```ts highlights={calculateTaxHighlights}
+// other imports...
+import {
+ calculateAmountsWithTax,
+} from "@medusajs/framework/utils"
+
+// ...
+products.forEach((product) => {
+ product.variants?.forEach((variant) => {
+ if (!variant.calculated_price) {
+ return
+ }
+
+ const taxLinesForVariant = taxLinesMap.get(variant.id) || []
+ const { priceWithTax, priceWithoutTax } = calculateAmountsWithTax({
+ taxLines: taxLinesForVariant,
+ amount: variant.calculated_price!.calculated_amount!,
+ includesTax:
+ variant.calculated_price!.is_calculated_price_tax_inclusive!,
+ })
+
+ // do something with prices...
+ })
+})
+```
+
+For each product variant, you:
+
+1. Retrieve its tax lines from the `taxLinesMap`.
+2. Calculate its prices with and without taxes using the `calculateAmountsWithTax` from the Medusa Framework.
+3. The `calculateAmountsWithTax` function returns an object having two properties:
+ - `priceWithTax`: The variant's price with the taxes applied.
+ - `priceWithoutTax`: The variant's price without taxes applied.
+
+
+# Get Product Variant Prices using Query
+
+In this document, you'll learn how to retrieve product variant prices in the Medusa application using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md).
+
+The Product Module doesn't provide pricing functionalities. The Medusa application links the Product Module's `ProductVariant` data model to the Pricing Module's `PriceSet` data model.
+
+So, to retrieve data across the linked records of the two modules, you use Query.
+
+## Retrieve All Product Variant Prices
+
+To retrieve all product variant prices, retrieve the product using Query and include among its fields `variants.prices.*`.
+
+For example:
+
+```ts highlights={[["6"]]}
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "*",
+ "variants.*",
+ "variants.prices.*",
+ ],
+ filters: {
+ id: [
+ "prod_123",
+ ],
+ },
+})
+```
+
+Each variant in the retrieved products has a `prices` array property with all the product variant prices. Each price object has the properties of the [Pricing Module's Price data model](https://docs.medusajs.com/references/pricing/models/Price/index.html.md).
+
+***
+
+## Retrieve Calculated Price for a Context
+
+The Pricing Module can calculate prices of a variant based on a [context](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), such as the region ID or the currency code.
+
+Learn more about prices calculation in [this Pricing Module documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md).
+
+To retrieve calculated prices of variants based on a context, retrieve the products using Query and:
+
+- Pass `variants.calculated_price.*` in the `fields` property.
+- Pass a `context` property in the object parameter. Its value is an object of objects that sets the context for the retrieved fields.
+
+For example:
+
+```ts highlights={[["10"], ["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"]]}
+import { QueryContext } from "@medusajs/framework/utils"
+
+// ...
+
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "*",
+ "variants.*",
+ "variants.calculated_price.*",
+ ],
+ filters: {
+ id: "prod_123",
+ },
+ context: {
+ variants: {
+ calculated_price: QueryContext({
+ region_id: "reg_01J3MRPDNXXXDSCC76Y6YCZARS",
+ currency_code: "eur",
+ }),
+ },
+ },
+})
+```
+
+For the context of the product variant's calculated price, you pass an object to `context` with the property `variants`, whose value is another object with the property `calculated_price`.
+
+`calculated_price`'s value is created using `QueryContext` from the Modules SDK, passing it a [calculation context object](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md).
+
+Each variant in the retrieved products has a `calculated_price` object. Learn more about its properties in [this Pricing Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
+
+
## Workflows
- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md)
-- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md)
- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md)
- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/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)
-- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md)
-- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
-- [createCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomersWorkflow/index.html.md)
-- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md)
-- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
-- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/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)
-- [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md)
-- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
-- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md)
-- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
-- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md)
-- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
-- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md)
-- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md)
+- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/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)
+- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/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)
- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/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)
- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md)
+- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md)
+- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/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)
-- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md)
-- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md)
+- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md)
- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md)
- [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md)
- [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md)
- [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/index.html.md)
- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md)
-- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md)
+- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/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)
+- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md)
+- [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md)
+- [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md)
+- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md)
+- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/index.html.md)
- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md)
-- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/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)
-- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
+- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/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)
+- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
- [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/index.html.md)
- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md)
- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/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)
+- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md)
-- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
+- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md)
+- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md)
- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
- [deleteServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteServiceZonesWorkflow/index.html.md)
- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/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)
+- [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md)
- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/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)
-- [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md)
+- [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md)
+- [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/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)
- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/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)
+- [createInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryItemsWorkflow/index.html.md)
- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md)
- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md)
-- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md)
+- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md)
+- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/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)
-- [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/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)
-- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
-- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/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)
-- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/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)
-- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
-- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/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)
-- [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)
+- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md)
+- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
+- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md)
+- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
-- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md)
- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
-- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/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)
- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md)
- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md)
-- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md)
-- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
+- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
+- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md)
+- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
-- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
- [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/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)
- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
- [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/index.html.md)
- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/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)
-- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
-- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md)
-- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/index.html.md)
+- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
+- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
- [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/index.html.md)
-- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/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)
+- [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)
-- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
-- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
-- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md)
+- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
+- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
+- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md)
- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
-- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
-- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/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)
+- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
+- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/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)
- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
-- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
-- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
+- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
+- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/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)
- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/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)
+- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
+- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
-- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/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)
+- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md)
+- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
+- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/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)
+- [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md)
- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/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)
-- [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md)
- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md)
-- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
+- [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md)
+- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md)
- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
-- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
+- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/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)
- [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md)
- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
-- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md)
+- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
-- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/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)
- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md)
- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
+- [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/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)
- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
+- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md)
- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/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)
- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
-- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
-- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
-- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md)
+- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/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)
-- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/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)
- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
-- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/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)
- [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)
-- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/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)
- [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md)
+- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
+- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md)
- [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md)
+- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
-- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md)
-- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/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)
+- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/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)
- [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md)
-- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md)
-- [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/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)
- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md)
- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/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)
- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
-- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
+- [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md)
- [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md)
+- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md)
+- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md)
-- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/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)
- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
-- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/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)
+- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/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)
- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md)
-- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
+- [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md)
- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
+- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md)
- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md)
+- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
- [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/index.html.md)
-- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
+- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md)
- [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/index.html.md)
+- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md)
-- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/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)
-- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md)
-- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
-- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/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)
-- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
+- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
+- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
+- [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/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)
+- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
+- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/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)
+- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/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)
+- [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)
+- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/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)
- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md)
-- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
-- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md)
- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
+- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md)
+- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
+- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
- [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md)
- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md)
-- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md)
- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
-- [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/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)
- [deleteProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductOptionsWorkflow/index.html.md)
+- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md)
- [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md)
- [deleteProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTagsWorkflow/index.html.md)
-- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md)
- [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md)
+- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md)
- [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md)
-- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
+- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md)
- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md)
-- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
+- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/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)
- [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md)
+- [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/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)
+- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md)
- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
-- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
+- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
-- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/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)
- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
-- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md)
-- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md)
-- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md)
- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/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)
+- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/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)
+- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
+- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
+- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
+- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/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)
-- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md)
- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/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)
- [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)
-- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/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)
-- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md)
- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md)
- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md)
-- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/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)
+- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md)
- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md)
-- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md)
-- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/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)
- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md)
+- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md)
- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/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)
@@ -26139,270 +26283,270 @@ When you set up the webhook in Stripe, choose the following events to listen to:
- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md)
- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md)
-- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/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)
+- [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
- [createApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/createApiKeysStep/index.html.md)
- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md)
-- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md)
- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/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)
- [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)
- [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)
+- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md)
- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/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)
-- [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)
+- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md)
- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
-- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/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)
-- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
-- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
-- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
-- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
-- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
-- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
-- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
-- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
-- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/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)
-- [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md)
-- [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md)
-- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md)
-- [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md)
-- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/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)
-- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/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)
-- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/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)
-- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
-- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md)
-- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
-- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
-- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
-- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/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)
- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/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)
-- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
-- [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/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)
+- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/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)
- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
-- [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/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)
+- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/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)
-- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/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)
- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
-- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
+- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md)
+- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/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)
- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/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)
- [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md)
- [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md)
- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md)
-- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md)
- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/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)
- [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)
-- [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)
+- [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md)
- [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/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)
-- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md)
+- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
+- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
+- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
+- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
+- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/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)
+- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
+- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
+- [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/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)
+- [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md)
+- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/index.html.md)
+- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/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)
+- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md)
+- [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md)
+- [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md)
+- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md)
+- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md)
+- [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md)
+- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
+- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
+- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
+- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
+- [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/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)
+- [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)
-- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/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)
- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
+- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md)
+- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
-- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/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)
-- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/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)
+- [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)
+- [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)
+- [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)
+- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/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)
+- [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)
+- [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)
+- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
+- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/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)
+- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md)
+- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
+- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
+- [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/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)
+- [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)
+- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/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)
+- [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)
- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
+- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/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)
+- [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md)
+- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md)
+- [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/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)
+- [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)
+- [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)
+- [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md)
+- [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/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)
- [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md)
-- [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md)
- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
+- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md)
+- [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md)
- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
-- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md)
- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
+- [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/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)
-- [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md)
-- [createOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderChangeStep/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)
+- [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)
+- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md)
- [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md)
- [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md)
-- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/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)
-- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md)
- [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md)
-- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md)
+- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/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)
+- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md)
- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md)
-- [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md)
- [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md)
-- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
-- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md)
- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
+- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/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)
+- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
- [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md)
-- [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md)
-- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md)
- [updateOrderShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderShippingMethodsStep/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)
- [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md)
-- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md)
-- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/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)
-- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/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)
-- [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)
-- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
-- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
-- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md)
-- [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/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)
-- [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)
-- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md)
-- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md)
-- [validateVariantPriceLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPriceLinksStep/index.html.md)
-- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/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)
-- [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)
+- [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)
- [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/index.html.md)
-- [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md)
-- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
+- [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md)
- [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md)
-- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md)
- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
+- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
+- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md)
- [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md)
- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
-- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md)
- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md)
+- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/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)
- [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)
- [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/index.html.md)
-- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
+- [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/index.html.md)
+- [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md)
- [getAllProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getAllProductsStep/index.html.md)
+- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
+- [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md)
- [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/index.html.md)
- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md)
-- [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md)
-- [updateProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductOptionsStep/index.html.md)
- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/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)
- [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)
- [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/index.html.md)
- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md)
-- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/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)
-- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md)
+- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md)
-- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/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)
-- [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)
-- [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)
-- [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)
-- [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)
-- [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)
-- [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)
-- [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)
-- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md)
-- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/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)
-- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
+- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/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)
- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/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)
- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/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)
- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/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)
+- [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)
+- [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/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)
+- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/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)
+- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/index.html.md)
- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md)
-- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/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)
+- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/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)
- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md)
-- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/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)
- [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)
-- [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)
-- [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md)
+- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
+- [listShippingOptionsForContextStep](https://docs.medusajs.com/references/medusa-workflows/steps/listShippingOptionsForContextStep/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)
+- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md)
+- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md)
+- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/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)
# Medusa CLI Reference
@@ -26428,68 +26572,6 @@ npx medusa --help
***
-# build Command - Medusa CLI Reference
-
-Create a standalone build of the Medusa application.
-
-This creates a build that:
-
-- Doesn't rely on the source TypeScript files.
-- Can be copied to a production server reliably.
-
-The build is outputted to a new `.medusa/server` directory.
-
-```bash
-npx medusa build
-```
-
-Refer to [this section](#run-built-medusa-application) for next steps.
-
-## Options
-
-|Option|Description|
-|---|---|---|
-|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
-
-***
-
-## Run Built Medusa Application
-
-After running the `build` command, use the following step to run the built Medusa application:
-
-- Change to the `.medusa/server` directory and install the dependencies:
-
-```bash npm2yarn
-cd .medusa/server && npm install
-```
-
-- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
-
-```bash npm2yarn
-cp .env .medusa/server/.env.production
-```
-
-- In the system environment variables, set `NODE_ENV` to `production`:
-
-```bash
-NODE_ENV=production
-```
-
-- Use the `start` command to run the application:
-
-```bash npm2yarn
-cd .medusa/server && npm run start
-```
-
-***
-
-## Build Medusa Admin
-
-By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
-
-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.
-
-
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -26610,22 +26692,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|
-
-
# 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.
@@ -26642,66 +26708,83 @@ npx medusa develop
|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-# plugin Commands - Medusa CLI Reference
+# build Command - Medusa CLI Reference
-Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
+Create a standalone build of the Medusa application.
-These commands are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+This creates a build that:
-## plugin:publish
+- Doesn't rely on the source TypeScript files.
+- Can be copied to a production server reliably.
-Publish a plugin into the local packages registry. The command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. You can then install the plugin in a local Medusa project using the [plugin:add](#pluginadd) command.
+The build is outputted to a new `.medusa/server` directory.
```bash
-npx medusa plugin:publish
+npx medusa build
```
+Refer to [this section](#run-built-medusa-application) for next steps.
+
+## Options
+
+|Option|Description|
+|---|---|---|
+|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the |
+
***
-## plugin:add
+## Run Built Medusa Application
-Install the specified plugins from the local package registry into a local Medusa application. Plugins can be added to the local package registry using the [plugin:publish](#pluginpublish) command.
+After running the `build` command, use the following step to run the built Medusa application:
-```bash
-npx medusa plugin:add [names...]
+- Change to the `.medusa/server` directory and install the dependencies:
+
+```bash npm2yarn
+cd .medusa/server && npm install
```
-### Arguments
+- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
-|Argument|Description|Required|
-|---|---|---|---|---|
-|\`names\`|The names of one or more plugins to install from the local package registry. A plugin's name is as specified in its |Yes|
+```bash npm2yarn
+cp .env .medusa/server/.env.production
+```
-***
+- In the system environment variables, set `NODE_ENV` to `production`:
-## plugin:develop
+```bash
+NODE_ENV=production
+```
-Start a development server for a plugin. The command will watch for changes in the plugin's source code and automatically re-publish the changes into the local package registry.
+- Use the `start` command to run the application:
-```bash
-npx medusa plugin:develop
+```bash npm2yarn
+cd .medusa/server && npm run start
```
***
-## plugin:db:generate
+## Build Medusa Admin
-Generate migrations for all modules in a plugin.
+By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
-```bash
-npx medusa plugin:db:generate
-```
+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.
-***
-## plugin:build
+# start Command - Medusa CLI Reference
-Build a plugin before publishing it to NPM. The command will compile an output in the `.medusa/server` directory.
+Start the Medusa application in production.
```bash
-npx medusa plugin:build
+npx medusa start
```
+## 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
@@ -26732,20 +26815,65 @@ medusa new [ []]
|\`--db-host \\`|The database host to use for database setup.|
-# start Command - Medusa CLI Reference
+# plugin Commands - Medusa CLI Reference
-Start the Medusa application in production.
+Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development.
+
+These commands are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+
+## plugin:publish
+
+Publish a plugin into the local packages registry. The command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. You can then install the plugin in a local Medusa project using the [plugin:add](#pluginadd) command.
```bash
-npx medusa start
+npx medusa plugin:publish
```
-## Options
+***
-|Option|Description|Default|
+## plugin:add
+
+Install the specified plugins from the local package registry into a local Medusa application. Plugins can be added to the local package registry using the [plugin:publish](#pluginpublish) command.
+
+```bash
+npx medusa plugin:add [names...]
+```
+
+### Arguments
+
+|Argument|Description|Required|
|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+|\`names\`|The names of one or more plugins to install from the local package registry. A plugin's name is as specified in its |Yes|
+
+***
+
+## plugin:develop
+
+Start a development server for a plugin. The command will watch for changes in the plugin's source code and automatically re-publish the changes into the local package registry.
+
+```bash
+npx medusa plugin:develop
+```
+
+***
+
+## plugin:db:generate
+
+Generate migrations for all modules in a plugin.
+
+```bash
+npx medusa plugin:db:generate
+```
+
+***
+
+## plugin:build
+
+Build a plugin before publishing it to NPM. The command will compile an output in the `.medusa/server` directory.
+
+```bash
+npx medusa plugin:build
+```
# start-cluster Command - Medusa CLI Reference
@@ -26767,6 +26895,22 @@ npx medusa start-cluster
|\`-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.|
+
+
# user Command - Medusa CLI Reference
Create a new admin user.
@@ -26786,20 +26930,20 @@ npx medusa user --email [--password ]
If ran successfully, you'll receive the invite token in the output.|No|\`false\`|
-# telemetry Command - Medusa CLI Reference
+# exec 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.
+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 telemetry
+npx medusa exec [file] [args...]
```
-#### Options
+## Arguments
-|Option|Description|
-|---|---|---|
-|\`--enable\`|Enable telemetry (default).|
-|\`--disable\`|Disable telemetry.|
+|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|
# Medusa CLI Reference
@@ -26854,37 +26998,98 @@ Refer to [this section](#run-built-medusa-application) for next steps.
After running the `build` command, use the following step to run the built Medusa application:
-- Change to the `.medusa/server` directory and install the dependencies:
+- Change to the `.medusa/server` directory and install the dependencies:
+
+```bash npm2yarn
+cd .medusa/server && npm install
+```
+
+- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
+
+```bash npm2yarn
+cp .env .medusa/server/.env.production
+```
+
+- In the system environment variables, set `NODE_ENV` to `production`:
+
+```bash
+NODE_ENV=production
+```
+
+- Use the `start` command to run the application:
+
+```bash npm2yarn
+cd .medusa/server && npm run start
+```
+
+***
+
+## Build Medusa Admin
+
+By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
+
+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\`|
+
+
+# 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 npm2yarn
-cd .medusa/server && npm install
+```bash
+medusa new [ []]
```
-- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead.
+## Arguments
-```bash npm2yarn
-cp .env .medusa/server/.env.production
-```
+|Argument|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-|
+|\`starter\_url\`|The name of the directory to create the Medusa application in.|No|\`https://github.com/medusajs/medusa-starter-default\`|
-- In the system environment variables, set `NODE_ENV` to `production`:
+## Options
-```bash
-NODE_ENV=production
-```
+|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.|
-- Use the `start` command to run the application:
-```bash npm2yarn
-cd .medusa/server && npm run start
-```
+# 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).
-## Build Medusa Admin
+```bash
+npx medusa exec [file] [args...]
+```
-By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory.
+## Arguments
-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.
+|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|
# db Commands - Medusa CLI Reference
@@ -27007,86 +27212,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|
-
-
-# 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 name of the directory to create the Medusa application in.|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.|
-
-
-# start-cluster Command - Medusa CLI Reference
-
-Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster).
-
-Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one.
-
-```bash
-npx medusa start-cluster
-```
-
-#### Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
# 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.
@@ -27148,20 +27273,20 @@ npx medusa plugin:build
```
-# telemetry Command - Medusa CLI Reference
+# start 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.
+Start the Medusa application in production.
```bash
-npx medusa telemetry
+npx medusa start
```
-#### Options
+## Options
-|Option|Description|
-|---|---|---|
-|\`--enable\`|Enable telemetry (default).|
-|\`--disable\`|Disable telemetry.|
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
# user Command - Medusa CLI Reference
@@ -27183,18 +27308,37 @@ npx medusa user --email [--password ]
If ran successfully, you'll receive the invite token in the output.|No|\`false\`|
-# start Command - Medusa CLI Reference
+# telemetry Command - Medusa CLI Reference
-Start the Medusa application in production.
+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 start
+npx medusa telemetry
```
-## Options
+#### Options
+
+|Option|Description|
+|---|---|---|
+|\`--enable\`|Enable telemetry (default).|
+|\`--disable\`|Disable telemetry.|
+
+
+# start-cluster Command - Medusa CLI Reference
+
+Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster).
+
+Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one.
+
+```bash
+npx medusa start-cluster
+```
+
+#### Options
|Option|Description|Default|
|---|---|---|---|---|
+|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.|
|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
|\`-p \\`|Set port of the Medusa server.|\`9000\`|
@@ -27499,250 +27643,228 @@ The object or class passed to `auth.storage` configuration must have the followi
## JS SDK Admin
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.list/index.html.md)
+- [batchSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.batchSalesChannels/index.html.md)
+- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/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)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md)
-- [batchSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.batchSalesChannels/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)
-- [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)
-- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/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)
- [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)
-- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md)
-- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/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)
+- [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)
-- [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)
-- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/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)
- [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)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md)
-- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
+- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md)
- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md)
-- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md)
+- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
+- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
+- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/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)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/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)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md)
- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
-- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/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)
-- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
-- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/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)
- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
+- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/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)
+- [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)
- [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)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
-- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/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/Currency/methods/js_sdk.admin.Currency.retrieve/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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/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)
+- [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)
+- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/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)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/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)
-- [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/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
-- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
-- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/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)
+- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md)
+- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md)
- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/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)
- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md)
-- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/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)
+- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md)
- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md)
-- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/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)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
+- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/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)
- [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)
- [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)
- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/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)
- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/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)
-- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/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/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
-- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
-- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/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)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
-- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/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)
+- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/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)
+- [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)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/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/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/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/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)
+- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/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)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancel/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)
- [cancelTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelTransfer/index.html.md)
+- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md)
- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md)
-- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md)
+- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
-- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md)
-- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/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)
-- [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/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)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
-- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/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)
-- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md)
-- [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/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)
-- [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/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/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/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)
+- [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)
+- [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)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/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/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
-- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/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)
+- [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)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/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)
+- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
+- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
+- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md)
- [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md)
-- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
-- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
-- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
+- [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/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)
+- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
+- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/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)
- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/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)
-- [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)
- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md)
- [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/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)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/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/ProductCollection/methods/js_sdk.admin.ProductCollection.create/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)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/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)
- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/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)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/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)
+- [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)
+- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
+- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/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)
- [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)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md)
- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/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)
-- [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md)
+- [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/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/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/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)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.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/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/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)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/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)
-- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/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)
-- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
-- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
-- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
-- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
-- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md)
-- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/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/Return/methods/js_sdk.admin.Return.retrieve/index.html.md)
-- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md)
-- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/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)
-- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md)
-- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/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)
@@ -27751,62 +27873,84 @@ The object or class passed to `auth.storage` configuration must have the followi
- [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)
-- [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)
- [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)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/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)
+- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md)
+- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md)
+- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md)
+- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
+- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
+- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
+- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/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)
+- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/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)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md)
+- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/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)
+- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/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)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/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/StockLocation/methods/js_sdk.admin.StockLocation.create/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)
+- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/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/ShippingProfile/methods/js_sdk.admin.ShippingProfile.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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md)
-- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
-- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md)
-- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/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)
-- [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/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)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/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)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/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)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md)
- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/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)
+- [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)
-- [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md)
+- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/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)
+- [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md)
## JS SDK Store
@@ -27814,10 +27958,10 @@ The object or class passed to `auth.storage` configuration must have the followi
- [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)
- [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)
-- [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)
- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
+- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/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)
@@ -28626,6 +28770,65 @@ 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.
@@ -28851,768 +29054,565 @@ export default ProductWidget
```
-# Container - Admin Components
+# Forms - Admin Components
-The Medusa Admin wraps each section of a page in a container.
+The Medusa Admin has two types of forms:
-
+1. Create forms, created using the [FocusModal UI component](https://docs.medusajs.com/ui/components/focus-modal/index.html.md).
+2. Edit or update forms, created using the [Drawer UI component](https://docs.medusajs.com/ui/components/drawer/index.html.md).
-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:
+This guide explains how to create these two form types following the Medusa Admin's conventions.
-```tsx
-import {
- Container as UiContainer,
- clx,
-} from "@medusajs/ui"
+## Form Tooling
-type ContainerProps = React.ComponentProps
+The Medusa Admin uses the following tools to build the forms:
-export const Container = (props: ContainerProps) => {
- return (
-
- )
-}
-```
+1. [react-hook-form](https://react-hook-form.com/) to easily build forms and manage their states.
+2. [Zod](https://zod.dev/) to validate the form's fields.
-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.
+Both of these libraries are available in your project, so you don't have to install them to use them.
***
-## 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
+## Create Form
-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.
+In this section, you'll build a form component to create an item of a resource.
-Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI:
+### Full Component
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- createDataTableColumnHelper,
+```tsx title="src/admin/components/create-form.tsx"
+import {
+ FocusModal,
+ Heading,
+ Label,
+ Input,
+ Button,
} from "@medusajs/ui"
import {
- HttpTypes,
-} from "@medusajs/framework/types"
+ useForm,
+ FormProvider,
+ Controller,
+} from "react-hook-form"
+import * as zod from "zod"
-const columnHelper = createDataTableColumnHelper()
+const schema = zod.object({
+ name: zod.string(),
+})
-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"}
-
- )
+export const CreateForm = () => {
+ const form = useForm>({
+ defaultValues: {
+ name: "",
},
- }),
-]
-```
-
-`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])
+ const handleSubmit = form.handleSubmit(({ name }) => {
+ // TODO submit to backend
+ console.log(name)
+ })
- // TODO add data fetching logic
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
}
```
-In the component, you've added the following state variables:
+Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has a create form in the admin.
-- `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.
+Start by creating the file `src/admin/components/create-form.tsx` that you'll create the form in.
-You've also added two memoized variables:
+### Create Validation Schema
-- `offset`: How many items to skip when fetching data based on the current page.
-- `statusFilters`: The selected status filters, if any.
+In `src/admin/components/create-form.tsx`, create a validation schema with Zod for the form's fields:
-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/components/create-form.tsx"
+import * as zod from "zod"
-```tsx title="src/admin/routes/custom/page.tsx"
-import { sdk } from "../../lib/config"
-import { useQuery } from "@tanstack/react-query"
+const schema = zod.object({
+ name: zod.string(),
+})
```
-This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
+The form in this guide is simple, it only has a required `name` field, which is a string.
-Then, replace the `TODO` in the component with the following:
+### Initialize Form
-```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]],
-})
+Next, you'll initialize the form using `react-hook-form`.
-// TODO configure data table
-```
+Add to `src/admin/components/create-form.tsx` the following:
-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:
+```tsx title="src/admin/components/create-form.tsx"
+// other imports...
+import { useForm } from "react-hook-form"
-- `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.
+// validation schema...
-So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
+export const CreateForm = () => {
+ const form = useForm>({
+ defaultValues: {
+ name: "",
+ },
+ })
-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:
+ const handleSubmit = form.handleSubmit(({ name }) => {
+ // TODO submit to backend
+ console.log(name)
+ })
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- useDataTable,
-} from "@medusajs/ui"
-import { useNavigate } from "react-router-dom"
+ // TODO render form
+}
```
-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}`)
- },
-})
+You create the `CreateForm` component. For now, it uses `useForm` from `react-hook-form` to initialize a form.
-// TODO render component
-```
+You also define a `handleSubmit` function to perform an action when the form is submitted.
-The `useDataTable` hook accepts an object with the following properties:
+You can replace the content of the function with sending a request to Medusa's routes. Refer to [this guide](https://docs.medusajs.com/docs/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md) for more details on how to do that.
-- 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.
+### Render Components
- - state: (\`object\`) The pagination React state variable.
+You'll now add a `return` statement that renders the focus modal where the form is shown.
- - onPaginationChange: (\`function\`) A function that updates the pagination state.
-- search: (\`object\`) An object to configure searching.
+Replace `// TODO render form` with the following:
- - state: (\`string\`) The search query React state variable.
+```tsx title="src/admin/components/create-form.tsx"
+// other imports...
+import {
+ FocusModal,
+ Heading,
+ Label,
+ Input,
+ Button,
+} from "@medusajs/ui"
+import {
+ FormProvider,
+ Controller,
+} from "react-hook-form"
- - onSearchChange: (\`function\`) A function that updates the search query state.
-- filtering: (\`object\`) An object to configure filtering.
+export const CreateForm = () => {
+ // ...
- - state: (\`object\`) The filtering React state variable.
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
+```
- - 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.
+You render a focus modal, with a trigger button to open it.
- - state: (\`object\`) The sorting React state variable.
+In the `FocusModal.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props.
- - 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.
+In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event.
- - event: (\`mouseevent\`) An instance of the \[MouseClickEvent]\(https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object.
+In the `FocusModal.Header` component, you add buttons to save or cancel the form submission.
- - row: (\`object\`) The data of the row that was clicked.
+Finally, you render the form's components inside the `FocusModal.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`.
-Finally, you'll render the data table. But first, add the following imports at the top of the page:
+### Use Create Form Component
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- DataTable,
-} from "@medusajs/ui"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Container } from "../../components/container"
-```
+You can use the `CreateForm` component in your widget or UI route.
-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.
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-Then, replace the `TODO` in the component with the following:
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { CreateForm } from "../components/create-form"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
-```tsx title="src/admin/routes/custom/page.tsx"
-return (
-
+const ProductWidget = () => {
+ 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.
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
-Lastly, export the component and the UI widget's configuration at the end of the file:
+export default ProductWidget
+```
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
+This component 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 components.
-// ...
+It will add at the top of a product's details page a new section, and in its header you'll find a Create button. If you click on it, it will open the focus modal with your form.
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
+***
-export default CustomPage
-```
+## Edit Form
-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.
+In this section, you'll build a form component to edit an item of a resource.
-### Full Example Code
+### Full Component
-```tsx title="src/admin/routes/custom/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
+```tsx title="src/admin/components/edit-form.tsx"
import {
- Badge,
- createDataTableColumnHelper,
- createDataTableFilterHelper,
- DataTable,
- DataTableFilteringState,
- DataTablePaginationState,
- DataTableSortingState,
+ Drawer,
Heading,
- useDataTable,
+ Label,
+ Input,
+ Button,
} 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"
+import {
+ useForm,
+ FormProvider,
+ Controller,
+} from "react-hook-form"
+import * as zod from "zod"
-const columnHelper = createDataTableColumnHelper()
+const schema = zod.object({
+ name: zod.string(),
+})
-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"}
-
- )
+export const EditForm = () => {
+ const form = useForm>({
+ defaultValues: {
+ name: "",
},
- }),
-]
-
-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,
- },
+ const handleSubmit = form.handleSubmit(({ name }) => {
+ // TODO submit to backend
+ console.log(name)
})
return (
-
-
-
-
- Products
-
-
-
-
+
+
+
+
+
+
+
+
+
+
)
}
+```
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
+Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has an edit form in the admin.
-export default CustomPage
+Start by creating the file `src/admin/components/edit-form.tsx` that you'll create the form in.
+
+### Create Validation Schema
+
+In `src/admin/components/edit-form.tsx`, create a validation schema with Zod for the form's fields:
+
+```tsx title="src/admin/components/edit-form.tsx"
+import * as zod from "zod"
+
+const schema = zod.object({
+ name: zod.string(),
+})
```
+The form in this guide is simple, it only has a required `name` field, which is a string.
-# JSON View - Admin Components
+### Initialize Form
-Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
+Next, you'll initialize the form using `react-hook-form`.
-
+Add to `src/admin/components/edit-form.tsx` the following:
-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/edit-form.tsx"
+// other imports...
+import { useForm } from "react-hook-form"
-```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"
+// validation schema...
-type JsonViewSectionProps = {
- data: object
- title?: string
-}
+export const EditForm = () => {
+ const form = useForm>({
+ defaultValues: {
+ name: "",
+ },
+ })
-export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
- const numberOfKeys = Object.keys(data).length
+ const handleSubmit = form.handleSubmit(({ name }) => {
+ // TODO submit to backend
+ console.log(name)
+ })
- return (
-
-
-
-
-
-
- )
+ // TODO render form
}
+```
-type CopiedProps = {
- style?: CSSProperties
- value: object | undefined
-}
+You create the `EditForm` component. For now, it uses `useForm` from `react-hook-form` to initialize a form.
-const Copied = ({ style, value }: CopiedProps) => {
- const [copied, setCopied] = useState(false)
+You also define a `handleSubmit` function to perform an action when the form is submitted.
- const handler = (e: MouseEvent) => {
- e.stopPropagation()
- setCopied(true)
+You can replace the content of the function with sending a request to Medusa's routes. Refer to [this guide](https://docs.medusajs.com/docs/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md) for more details on how to do that.
- if (typeof value === "string") {
- navigator.clipboard.writeText(value)
- } else {
- const json = JSON.stringify(value, null, 2)
- navigator.clipboard.writeText(json)
- }
+### Render Components
- setTimeout(() => {
- setCopied(false)
- }, 2000)
- }
+You'll now add a `return` statement that renders the drawer where the form is shown.
- const styl = { whiteSpace: "nowrap", width: "20px" }
+Replace `// TODO render form` with the following:
- if (copied) {
- return (
-
-
-
- )
- }
+```tsx title="src/admin/components/edit-form.tsx"
+// other imports...
+import {
+ Drawer,
+ Heading,
+ Label,
+ Input,
+ Button,
+} from "@medusajs/ui"
+import {
+ FormProvider,
+ Controller,
+} from "react-hook-form"
+
+export const EditForm = () => {
+ // ...
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.
+You render a drawer, with a trigger button to open it.
-The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
+In the `Drawer.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props.
-***
+In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event.
-## Example
+You render the form's components inside the `Drawer.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`.
-Use the `JsonViewSection` component in any widget or UI route.
+Finally, in the `Drawer.Footer` component, you add buttons to save or cancel the form submission.
+
+### Use Edit Form Component
+
+You can use the `EditForm` component in your 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"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+import { EditForm } from "../components/edit-form"
const ProductWidget = () => {
- return
+ return (
+
+ ,
+ },
+ ]}
+ />
+
+ )
}
export const config = defineWidgetConfig({
@@ -29622,7 +29622,9 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
+This component 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 components.
+
+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
@@ -29703,248 +29705,59 @@ export const Header = ({
}
```
-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.
-
-
-# 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.
-
-You can use the `Table` component from Medusa UI to display data in a table. It's mostly recommended for simpler tables.
-
-To create a component that shows a table with pagination, create the file `src/admin/components/table.tsx` with the following content:
-
-```tsx title="src/admin/components/table.tsx"
-import { useMemo } from "react"
-import { Table as UiTable } from "@medusajs/ui"
-
-export type TableProps = {
- columns: {
- key: string
- label?: string
- render?: (value: unknown) => React.ReactNode
- }[]
- data: Record[]
- pageSize: number
- count: number
- currentPage: number
- setCurrentPage: (value: number) => void
-}
-
-export const Table = ({
- columns,
- data,
- pageSize,
- count,
- currentPage,
- setCurrentPage,
-}: TableProps) => {
- const pageCount = useMemo(() => {
- return Math.ceil(count / pageSize)
- }, [data, pageSize])
-
- const canNextPage = useMemo(() => {
- return currentPage < pageCount - 1
- }, [currentPage, pageCount])
- const canPreviousPage = useMemo(() => {
- return currentPage - 1 >= 0
- }, [currentPage])
-
- const nextPage = () => {
- if (canNextPage) {
- setCurrentPage(currentPage + 1)
- }
- }
-
- const previousPage = () => {
- if (canPreviousPage) {
- setCurrentPage(currentPage - 1)
- }
- }
-
- return (
-
- )
-}
-```
-
-The `Table` component uses the component from the [UI package](https://docs.medusajs.com/ui/components/table/index.html.md), with additional styling and rendering of data.
+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:
-- columns: (\`object\[]\`) The table's columns.
+- title: (\`string\`) The section's title.
+- subtitle: (\`string\`) The section's subtitle.
+- actions: (\`object\[]\`) An array of actions to show.
- - key: (\`string\`) The column's key in the passed \`data\`
+ - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add.
- - label: (\`string\`) The column's label shown in the table. If not provided, the \`key\` is used.
+ \- If its value is \`button\`, it'll show a button that can have a link or an on-click action.
- - render: (\`(value: unknown) => React.ReactNode\`) By default, the data is shown as-is in the table. You can use this function to change how the value is rendered. The function receives the value is a parameter and returns a React node.
-- data: (\`Record\\[]\`) The data to show in the table for the current page. The keys of each object should be in the \`columns\` array.
-- pageSize: (\`number\`) The number of items to show per page.
-- count: (\`number\`) The total number of items.
-- currentPage: (\`number\`) A zero-based index indicating the current page's number.
-- setCurrentPage: (\`(value: number) => void\`) A function used to change the current page.
+ \- 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 `Table` component in any widget or UI route.
+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 { StatusBadge } from "@medusajs/ui"
-import { Table } from "../components/table"
-import { useState } from "react"
import { Container } from "../components/container"
+import { Header } from "../components/header"
const ProductWidget = () => {
- const [currentPage, setCurrentPage] = useState(0)
-
return (
-
{
- const isEnabled = value as boolean
-
- return (
-
- {isEnabled ? "Enabled" : "Disabled"}
-
- )
+ type: "button",
+ props: {
+ children: "Click me",
+ variant: "secondary",
+ onClick: () => {
+ alert("You clicked the button.")
+ },
},
},
]}
- data={[
- {
- name: "John",
- is_enabled: true,
- },
- {
- name: "Jane",
- is_enabled: false,
- },
- ]}
- pageSize={2}
- count={2}
- currentPage={currentPage}
- setCurrentPage={setCurrentPage}
/>
)
@@ -29957,488 +29770,802 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-This widget also uses the [Container](../container.mdx) custom component.
+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.
-***
-## Example With Data Fetching
+# JSON View - Admin Components
-This section shows you how to use the `Table` component when fetching data from the Medusa application's API routes.
+Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
-Assuming you've set up the JS SDK as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), create the UI route `src/admin/routes/custom/page.tsx` with the following content:
+
-```tsx title="src/admin/routes/custom/page.tsx" collapsibleLines="1-10" expandButtonLabel="Show Imports" highlights={tableExampleHighlights}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { useQuery } from "@tanstack/react-query"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Table } from "../../components/table"
-import { sdk } from "../../lib/config"
-import { useMemo, useState } from "react"
-import { Container } from "../../components/container"
-import { Header } from "../../components/header"
+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:
-const CustomPage = () => {
- const [currentPage, setCurrentPage] = useState(0)
- const limit = 15
- const offset = useMemo(() => {
- return currentPage * limit
- }, [currentPage])
+```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"
- const { data } = useQuery({
- queryFn: () => sdk.admin.product.list({
- limit,
- offset,
- }),
- queryKey: [["products", limit, offset]],
- })
+type JsonViewSectionProps = {
+ data: object
+ title?: string
+}
- // TODO display table
+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
+ }}
+ />
+
+
+
+
+
+
+
+ )
}
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
+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 CustomPage
+export default ProductWidget
```
-In the `CustomPage` component, you define:
+This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
-- A state variable `currentPage` that stores the current page of the table.
-- A `limit` variable, indicating how many items to retrieve per page
-- An `offset` memoized variable indicating how many items to skip before the retrieved items. It's calculated as a multiplication of `currentPage` and `limit`.
-Then, you use `useQuery` from [Tanstack Query](https://tanstack.com/query/latest) to retrieve products using the JS SDK. You pass `limit` and `offset` as query parameters, and you set the `queryKey`, which is used for caching and revalidation, to be based on the key `products`, along with the current limit and offset. So, whenever the `offset` variable changes, the request is sent again to retrieve the products of the current page.
+# Data Table - Admin Components
-You can change the query to send a request to a custom API route as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk#send-requests-to-custom-routes/index.html.md).
+This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0).
-Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
+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.
-`useQuery` returns an object containing `data`, which holds the response fields including the products and pagination fields.
+
-Then, to display the table, replace the `TODO` with the following:
+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.
-```tsx
-return (
-
-
-
- {data && (
-
- )}
-
-
-)
+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"}
+
+ )
+ },
+ }),
+]
```
-Aside from the `Table` component, this UI route also uses the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md), [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.
+`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:
-If `data` isn't `undefined`, you display the `Table` component passing it the following props:
+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".
-- `columns`: The columns to show. You only show the product's ID and title.
-- `data`: The rows of the table. You pass it the `products` property of `data`.
-- `pageSize`: The maximum number of items per page. You pass it the `count` property of `data`.
-- `currentPage` and `setCurrentPage`: The current page and the function to change it.
+Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.
-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.
+To define the filters, add the following:
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ createDataTableFilterHelper,
+} from "@medusajs/ui"
-# Section Row - Admin Components
+const filterHelper = createDataTableFilterHelper()
-The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
+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:
-To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content:
+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.
-```tsx title="src/admin/components/section-row.tsx"
-import { Text, clx } from "@medusajs/ui"
+You'll now start creating the UI widget's component. Start by adding the necessary state variables:
-export type SectionRowProps = {
- title: string
- value?: React.ReactNode | string | null
- actions?: React.ReactNode
-}
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ DataTablePaginationState,
+ DataTableFilteringState,
+ DataTableSortingState,
+} from "@medusajs/ui"
+import { useMemo, useState } from "react"
-export const SectionRow = ({ title, value, actions }: SectionRowProps) => {
- const isValueString = typeof value === "string" || !value
+// ...
- return (
-
- )
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
+
+ // TODO add data fetching logic
}
```
-The `SectionRow` component shows a title and a value in the same row.
-
-It accepts the following props:
+In the component, you've added the following state variables:
-- title: (\`string\`) The title to show on the left side.
-- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side.
-- actions: (\`React.ReactNode\`) The actions to show at the end of the row.
+- `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:
-## Example
+- `offset`: How many items to skip when fetching data based on the current page.
+- `statusFilters`: The selected status filters, if any.
-Use the `SectionRow` component in any widget or UI route.
+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:
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+```tsx title="src/admin/routes/custom/page.tsx"
+import { sdk } from "../../lib/config"
+import { useQuery } from "@tanstack/react-query"
+```
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-import { SectionRow } from "../components/section-row"
+This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
-const ProductWidget = () => {
- return (
-
-
-
-
- )
-}
+Then, replace the `TODO` in the component with the following:
-export const config = defineWidgetConfig({
- zone: "product.details.before",
+```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]],
})
-export default ProductWidget
+// TODO configure data table
```
-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.
+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.
-# Forms - Admin Components
+So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
-The Medusa Admin has two types of forms:
+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:
-1. Create forms, created using the [FocusModal UI component](https://docs.medusajs.com/ui/components/focus-modal/index.html.md).
-2. Edit or update forms, created using the [Drawer UI component](https://docs.medusajs.com/ui/components/drawer/index.html.md).
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ useDataTable,
+} from "@medusajs/ui"
+import { useNavigate } from "react-router-dom"
+```
-This guide explains how to create these two form types following the Medusa Admin's conventions.
+Then, replace the `TODO` in the component with the following:
-## Form Tooling
+```tsx title="src/admin/routes/custom/page.tsx"
+const navigate = useNavigate()
-The Medusa Admin uses the following tools to build the forms:
+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}`)
+ },
+})
-1. [react-hook-form](https://react-hook-form.com/) to easily build forms and manage their states.
-2. [Zod](https://zod.dev/) to validate the form's fields.
+// TODO render component
+```
-Both of these libraries are available in your project, so you don't have to install them to use them.
+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.
-## Create Form
+ - state: (\`object\`) The pagination React state variable.
-In this section, you'll build a form component to create an item of a resource.
+ - onPaginationChange: (\`function\`) A function that updates the pagination state.
+- search: (\`object\`) An object to configure searching.
-### Full Component
+ - state: (\`string\`) The search query React state variable.
-```tsx title="src/admin/components/create-form.tsx"
-import {
- FocusModal,
- Heading,
- Label,
- Input,
- Button,
-} from "@medusajs/ui"
-import {
- useForm,
- FormProvider,
- Controller,
-} from "react-hook-form"
-import * as zod from "zod"
+ - onSearchChange: (\`function\`) A function that updates the search query state.
+- filtering: (\`object\`) An object to configure filtering.
-const schema = zod.object({
- name: zod.string(),
-})
+ - state: (\`object\`) The filtering React state variable.
-export const CreateForm = () => {
- const form = useForm>({
- defaultValues: {
- name: "",
- },
- })
+ - 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.
- const handleSubmit = form.handleSubmit(({ name }) => {
- // TODO submit to backend
- console.log(name)
- })
+ - state: (\`object\`) The sorting React state variable.
- return (
-
-
-
-
-
-
-
-
-
-
- )
-}
-```
+ - 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.
-Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has a create form in the admin.
+ - event: (\`mouseevent\`) An instance of the \[MouseClickEvent]\(https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) object.
-Start by creating the file `src/admin/components/create-form.tsx` that you'll create the form in.
+ - row: (\`object\`) The data of the row that was clicked.
-### Create Validation Schema
+Finally, you'll render the data table. But first, add the following imports at the top of the page:
-In `src/admin/components/create-form.tsx`, create a validation schema with Zod for the form's fields:
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ DataTable,
+} from "@medusajs/ui"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Container } from "../../components/container"
+```
-```tsx title="src/admin/components/create-form.tsx"
-import * as zod from "zod"
+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.
-const schema = zod.object({
- name: zod.string(),
-})
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+return (
+
+
+
+
+ Products
+
+
+
+
+
+
+
+
+
+
+
+)
```
-The form in this guide is simple, it only has a required `name` field, which is a string.
+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.
-### Initialize Form
+Lastly, export the component and the UI widget's configuration at the end of the file:
-Next, you'll initialize the form using `react-hook-form`.
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
-Add to `src/admin/components/create-form.tsx` the following:
+// ...
-```tsx title="src/admin/components/create-form.tsx"
-// other imports...
-import { useForm } from "react-hook-form"
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
-// validation schema...
+export default CustomPage
+```
-export const CreateForm = () => {
- const form = useForm>({
- defaultValues: {
- name: "",
- },
- })
+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.
- const handleSubmit = form.handleSubmit(({ name }) => {
- // TODO submit to backend
- console.log(name)
- })
+### Full Example Code
- // TODO render form
-}
-```
+```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"
-You create the `CreateForm` component. For now, it uses `useForm` from `react-hook-form` to initialize a form.
+const columnHelper = createDataTableColumnHelper()
-You also define a `handleSubmit` function to perform an action when the form is submitted.
+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"}
+
+ )
+ },
+ }),
+]
-You can replace the content of the function with sending a request to Medusa's routes. Refer to [this guide](https://docs.medusajs.com/docs/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md) for more details on how to do that.
+const filterHelper = createDataTableFilterHelper()
-### Render Components
+const filters = [
+ filterHelper.accessor("status", {
+ type: "select",
+ label: "Status",
+ options: [
+ {
+ label: "Published",
+ value: "published",
+ },
+ {
+ label: "Draft",
+ value: "draft",
+ },
+ ],
+ }),
+]
-You'll now add a `return` statement that renders the focus modal where the form is shown.
+const limit = 15
-Replace `// TODO render form` with the following:
+const CustomPage = () => {
+ const [pagination, setPagination] = useState({
+ pageSize: limit,
+ pageIndex: 0,
+ })
+ const [search, setSearch] = useState("")
+ const [filtering, setFiltering] = useState({})
+ const [sorting, setSorting] = useState(null)
-```tsx title="src/admin/components/create-form.tsx"
-// other imports...
-import {
- FocusModal,
- Heading,
- Label,
- Input,
- Button,
-} from "@medusajs/ui"
-import {
- FormProvider,
- Controller,
-} from "react-hook-form"
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
-export const CreateForm = () => {
- // ...
+ 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
```
-You render a focus modal, with a trigger button to open it.
-In the `FocusModal.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props.
+# Section Row - Admin Components
-In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event.
+The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
-In the `FocusModal.Header` component, you add buttons to save or cancel the form submission.
+
-Finally, you render the form's components inside the `FocusModal.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`.
+To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content:
-### Use Create Form Component
+```tsx title="src/admin/components/section-row.tsx"
+import { Text, clx } from "@medusajs/ui"
-You can use the `CreateForm` component in your widget or UI route.
+export type SectionRowProps = {
+ title: string
+ value?: React.ReactNode | string | null
+ actions?: React.ReactNode
+}
+
+export const SectionRow = ({ title, value, actions }: SectionRowProps) => {
+ const isValueString = typeof value === "string" || !value
+
+ return (
+
+ )
+}
+```
+
+The `SectionRow` component shows a title and a value in the same row.
+
+It accepts the following props:
+
+- title: (\`string\`) The title to show on the left side.
+- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side.
+- actions: (\`React.ReactNode\`) The actions to show at the end of the row.
+
+***
+
+## Example
+
+Use the `SectionRow` 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 { CreateForm } from "../components/create-form"
import { Container } from "../components/container"
import { Header } from "../components/header"
+import { SectionRow } from "../components/section-row"
const ProductWidget = () => {
return (
- ,
- },
- ]}
- />
+
+
)
}
@@ -30450,280 +30577,297 @@ export const config = defineWidgetConfig({
export default ProductWidget
```
-This component 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 components.
+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.
-It will add at the top of a product's details page a new section, and in its header you'll find a Create button. If you click on it, it will open the focus modal with your form.
-***
+# Table - Admin Components
-## Edit Form
+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.
-In this section, you'll build a form component to edit an item of a resource.
+You can use the `Table` component from Medusa UI to display data in a table. It's mostly recommended for simpler tables.
-### Full Component
+To create a component that shows a table with pagination, create the file `src/admin/components/table.tsx` with the following content:
-```tsx title="src/admin/components/edit-form.tsx"
-import {
- Drawer,
- Heading,
- Label,
- Input,
- Button,
-} from "@medusajs/ui"
-import {
- useForm,
- FormProvider,
- Controller,
-} from "react-hook-form"
-import * as zod from "zod"
+```tsx title="src/admin/components/table.tsx"
+import { useMemo } from "react"
+import { Table as UiTable } from "@medusajs/ui"
-const schema = zod.object({
- name: zod.string(),
-})
+export type TableProps = {
+ columns: {
+ key: string
+ label?: string
+ render?: (value: unknown) => React.ReactNode
+ }[]
+ data: Record[]
+ pageSize: number
+ count: number
+ currentPage: number
+ setCurrentPage: (value: number) => void
+}
-export const EditForm = () => {
- const form = useForm>({
- defaultValues: {
- name: "",
- },
- })
+export const Table = ({
+ columns,
+ data,
+ pageSize,
+ count,
+ currentPage,
+ setCurrentPage,
+}: TableProps) => {
+ const pageCount = useMemo(() => {
+ return Math.ceil(count / pageSize)
+ }, [data, pageSize])
- const handleSubmit = form.handleSubmit(({ name }) => {
- // TODO submit to backend
- console.log(name)
- })
+ const canNextPage = useMemo(() => {
+ return currentPage < pageCount - 1
+ }, [currentPage, pageCount])
+ const canPreviousPage = useMemo(() => {
+ return currentPage - 1 >= 0
+ }, [currentPage])
+
+ const nextPage = () => {
+ if (canNextPage) {
+ setCurrentPage(currentPage + 1)
+ }
+ }
+
+ const previousPage = () => {
+ if (canPreviousPage) {
+ setCurrentPage(currentPage - 1)
+ }
+ }
return (
-
-
-
-
-
-
-
-
-
-
+
)
}
```
-Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has an edit form in the admin.
-
-Start by creating the file `src/admin/components/edit-form.tsx` that you'll create the form in.
+The `Table` component uses the component from the [UI package](https://docs.medusajs.com/ui/components/table/index.html.md), with additional styling and rendering of data.
-### Create Validation Schema
+It accepts the following props:
-In `src/admin/components/edit-form.tsx`, create a validation schema with Zod for the form's fields:
+- columns: (\`object\[]\`) The table's columns.
-```tsx title="src/admin/components/edit-form.tsx"
-import * as zod from "zod"
+ - key: (\`string\`) The column's key in the passed \`data\`
-const schema = zod.object({
- name: zod.string(),
-})
-```
+ - label: (\`string\`) The column's label shown in the table. If not provided, the \`key\` is used.
-The form in this guide is simple, it only has a required `name` field, which is a string.
+ - render: (\`(value: unknown) => React.ReactNode\`) By default, the data is shown as-is in the table. You can use this function to change how the value is rendered. The function receives the value is a parameter and returns a React node.
+- data: (\`Record\\[]\`) The data to show in the table for the current page. The keys of each object should be in the \`columns\` array.
+- pageSize: (\`number\`) The number of items to show per page.
+- count: (\`number\`) The total number of items.
+- currentPage: (\`number\`) A zero-based index indicating the current page's number.
+- setCurrentPage: (\`(value: number) => void\`) A function used to change the current page.
-### Initialize Form
+***
-Next, you'll initialize the form using `react-hook-form`.
+## Example
-Add to `src/admin/components/edit-form.tsx` the following:
+Use the `Table` component in any widget or UI route.
-```tsx title="src/admin/components/edit-form.tsx"
-// other imports...
-import { useForm } from "react-hook-form"
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-// validation schema...
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { StatusBadge } from "@medusajs/ui"
+import { Table } from "../components/table"
+import { useState } from "react"
+import { Container } from "../components/container"
-export const EditForm = () => {
- const form = useForm>({
- defaultValues: {
- name: "",
- },
- })
+const ProductWidget = () => {
+ const [currentPage, setCurrentPage] = useState(0)
- const handleSubmit = form.handleSubmit(({ name }) => {
- // TODO submit to backend
- console.log(name)
- })
+ return (
+
+
{
+ const isEnabled = value as boolean
- // TODO render form
+ return (
+
+ {isEnabled ? "Enabled" : "Disabled"}
+
+ )
+ },
+ },
+ ]}
+ data={[
+ {
+ name: "John",
+ is_enabled: true,
+ },
+ {
+ name: "Jane",
+ is_enabled: false,
+ },
+ ]}
+ pageSize={2}
+ count={2}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ />
+
+ )
}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
```
-You create the `EditForm` component. For now, it uses `useForm` from `react-hook-form` to initialize a form.
+This widget also uses the [Container](../container.mdx) custom component.
-You also define a `handleSubmit` function to perform an action when the form is submitted.
+***
-You can replace the content of the function with sending a request to Medusa's routes. Refer to [this guide](https://docs.medusajs.com/docs/learn/fundamentals/admin/tips#send-requests-to-api-routes/index.html.md) for more details on how to do that.
+## Example With Data Fetching
-### Render Components
+This section shows you how to use the `Table` component when fetching data from the Medusa application's API routes.
-You'll now add a `return` statement that renders the drawer where the form is shown.
+Assuming you've set up the JS SDK as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), create the UI route `src/admin/routes/custom/page.tsx` with the following content:
-Replace `// TODO render form` with the following:
+```tsx title="src/admin/routes/custom/page.tsx" collapsibleLines="1-10" expandButtonLabel="Show Imports" highlights={tableExampleHighlights}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { useQuery } from "@tanstack/react-query"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Table } from "../../components/table"
+import { sdk } from "../../lib/config"
+import { useMemo, useState } from "react"
+import { Container } from "../../components/container"
+import { Header } from "../../components/header"
-```tsx title="src/admin/components/edit-form.tsx"
-// other imports...
-import {
- Drawer,
- Heading,
- Label,
- Input,
- Button,
-} from "@medusajs/ui"
-import {
- FormProvider,
- Controller,
-} from "react-hook-form"
+const CustomPage = () => {
+ const [currentPage, setCurrentPage] = useState(0)
+ const limit = 15
+ const offset = useMemo(() => {
+ return currentPage * limit
+ }, [currentPage])
-export const EditForm = () => {
- // ...
+ const { data } = useQuery({
+ queryFn: () => sdk.admin.product.list({
+ limit,
+ offset,
+ }),
+ queryKey: [["products", limit, offset]],
+ })
- return (
-
-
-
-
-
-
-
-
-
-
- )
+ // TODO display table
}
-```
-You render a drawer, with a trigger button to open it.
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
-In the `Drawer.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props.
+export default CustomPage
+```
-In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event.
+In the `CustomPage` component, you define:
-You render the form's components inside the `Drawer.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`.
+- A state variable `currentPage` that stores the current page of the table.
+- A `limit` variable, indicating how many items to retrieve per page
+- An `offset` memoized variable indicating how many items to skip before the retrieved items. It's calculated as a multiplication of `currentPage` and `limit`.
-Finally, in the `Drawer.Footer` component, you add buttons to save or cancel the form submission.
+Then, you use `useQuery` from [Tanstack Query](https://tanstack.com/query/latest) to retrieve products using the JS SDK. You pass `limit` and `offset` as query parameters, and you set the `queryKey`, which is used for caching and revalidation, to be based on the key `products`, along with the current limit and offset. So, whenever the `offset` variable changes, the request is sent again to retrieve the products of the current page.
-### Use Edit Form Component
+You can change the query to send a request to a custom API route as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk#send-requests-to-custom-routes/index.html.md).
-You can use the `EditForm` component in your widget or UI route.
+Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency.
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+`useQuery` returns an object containing `data`, which holds the response fields including the products and pagination fields.
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-import { EditForm } from "../components/edit-form"
+Then, to display the table, replace the `TODO` with the following:
-const ProductWidget = () => {
- return (
+```tsx
+return (
+
- ,
- },
- ]}
- />
+
+ {data && (
+
+ )}
- )
-}
+
+)
+```
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
+Aside from the `Table` component, this UI route also uses the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md), [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.
-export default ProductWidget
-```
+If `data` isn't `undefined`, you display the `Table` component passing it the following props:
-This component 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 components.
+- `columns`: The columns to show. You only show the product's ID and title.
+- `data`: The rows of the table. You pass it the `products` property of `data`.
+- `pageSize`: The maximum number of items per page. You pass it the `count` property of `data`.
+- `currentPage` and `setCurrentPage`: The current page and the function to change it.
-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.
+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.
# Service Factory Reference
@@ -30753,6 +30897,149 @@ Some examples of method names:
The reference uses only the operation name to refer to the method.
+# 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.
+
+This guide provides examples of using filters.
+
+The `list` method is used in the example snippets of this reference, but you can use the same filtering mechanism with any method that accepts filters.
+
+***
+
+## Match Exact Value
+
+```ts
+const posts = await postModuleService.listPosts({
+ name: "My Post 2",
+})
+```
+
+If you pass a property with its value, only records whose properties exactly match the value are selected.
+
+In the example above, only posts having the name `My Post 2` are retrieved.
+
+***
+
+## Match Multiple Values
+
+```ts
+const posts = await postModuleService.listPosts({
+ views: [
+ 50,
+ 100,
+ ],
+})
+```
+
+To find records with a property matching multiple values, pass an array of those values as the property's value in the filter.
+
+In the example above, only posts having either `50` or `100` views are retrieved.
+
+***
+
+## Don't Match Values
+
+```ts
+const posts = await postModuleService.listPosts({
+ name: {
+ $nin: [
+ "My Post",
+ ],
+ },
+})
+```
+
+To find records with a property that doesn't match one or more values, pass an object with a `$nin` property. Its value is an array of multiple values that a record's property shouldn't match.
+
+In the example above, only posts that don't have the name `My Post` are retrieved.
+
+***
+
+## Match Text Like Value
+
+This filter only applies to text-like properties, including `id` and `enum` properties.
+
+```ts
+const posts = await postModuleService.listPosts({
+ name: {
+ $like: "My%",
+ },
+})
+```
+
+To perform a `like` filter on a record's property, set the property's value to an object with a `$like` property. Its value is the string to use when applying the `like` filter.
+
+The example above matches all posts whose name starts with `My`.
+
+***
+
+## Apply Range Filters
+
+This filter only applies to the `number` and `dateTime` properties.
+
+```ts
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $lt: new Date(),
+ },
+})
+```
+
+To filter a record's property to be within a range, set the property's value to an object with any of the following properties:
+
+1. `$lt`: The property's value must be less than the supplied value.
+2. `$lte`: The property's value must be less than or equal to the supplied value.
+3. `$gt`: The property's value must be greater than the supplied value.
+4. `$gte`: The property's value must be greater than or equal the supplied value.
+
+In the example above, only posts whose `published_at` property is before the current date and time are retrieved.
+
+### Example: Retrieve Posts Published Today
+
+```ts
+const startToday = new Date()
+startToday.setHours(0, 0, 0, 0)
+
+const endToday = new Date()
+endToday.setHours(23, 59, 59, 59)
+
+const posts = await postModuleService.listPosts({
+ published_at: {
+ $gte: startToday,
+ $lte: endToday,
+ },
+})
+```
+
+The `dateTime` property also stores the time. So, when matching for an exact day, you must set a range filter to be between the beginning and end of the day.
+
+In this example, you retrieve the current date twice: once to set its time to `00:00:00`, and another to set its time `23:59:59`. Then, you retrieve posts whose `published_at` property is between `00:00:00` and `23:59:59` of today.
+
+***
+
+## Apply Or Condition
+
+```ts
+const posts = await postModuleService.listPosts({
+ $or: [
+ {
+ name: "My Post",
+ },
+ {
+ published_at: {
+ $lt: new Date(),
+ },
+ },
+ ],
+})
+```
+
+To use an `or` condition, pass to the filter object the `$or` property, whose value is an array of filters.
+
+In the example above, posts whose name is `My Post` or their `published_at` date is less than the current date and time are retrieved.
+
+
# create Method - Service Factory Reference
This method creates one or more records of the data model.
@@ -30791,64 +31078,145 @@ const posts = await postModuleService.createPosts([
If an array is passed of the method, an array of the created records is also returned.
-# delete Method - Service Factory Reference
+# list Method - Service Factory Reference
-This method deletes one or more records.
+This method retrieves a list of records.
-## Delete One Record
+## Retrieve List of Records
```ts
-await postModuleService.deletePosts("123")
+const posts = await postModuleService.listPosts()
```
-To delete one record, pass its ID as a parameter of the method.
+If no parameters are passed, the method returns an array of the first `15` records.
***
-## Delete Multiple Records
+## Filter Records
```ts
-await postModuleService.deletePosts([
- "123",
- "321",
-])
+const posts = await postModuleService.listPosts({
+ id: ["123", "321"],
+})
```
-To delete multiple records, pass an array of IDs as a parameter of the method.
+### 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.
***
-## Delete Records Matching 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
-await postModuleService.deletePosts({
- name: "My Post",
+const posts = await postModuleService.listPosts({}, {
+ relations: ["author"],
})
```
-To delete records matching a set of filters, pass an object of filters as a parameter.
+### Parameters
-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).
+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
-# list Method - Service Factory Reference
+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.
-This method retrieves a list of records.
+
+# listAndCount Method - Service Factory Reference
+
+This method retrieves a list of records with the total count.
## Retrieve List of Records
```ts
-const posts = await postModuleService.listPosts()
+const [posts, count] = await postModuleService.listAndCountPosts()
```
-If no parameters are passed, the method returns an array of the first `15` records.
+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 = await postModuleService.listPosts({
+const [posts, count] = await postModuleService.listAndCountPosts({
id: ["123", "321"],
})
```
@@ -30861,7 +31229,10 @@ Learn more about accepted filters in [this documentation](https://docs.medusajs.
### Returns
-The method returns an array of the first `15` records matching the filters.
+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.
***
@@ -30870,25 +31241,28 @@ The method returns an array of the first `15` records matching the filters.
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({}, {
+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. `relations`'s value is an array of relation names.
+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 of the first `15` records matching the filters.
+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 = await postModuleService.listPosts({}, {
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
select: ["id", "name"],
})
```
@@ -30901,14 +31275,17 @@ By default, retrieved records have all their properties. To select specific prop
### Returns
-The method returns an array of the first `15` records matching the filters.
+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 = await postModuleService.listPosts({}, {
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
take: 20,
skip: 10,
})
@@ -30923,14 +31300,17 @@ To paginate the returned records, the second object parameter accepts the follow
### Returns
-The method returns an array of records. The number of records is less than or equal to `take`'s value.
+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 = await postModuleService.listPosts({}, {
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
order: {
name: "ASC",
},
@@ -30946,7 +31326,10 @@ To sort records by one or more properties, pass to the second object parameter t
### Returns
-The method returns an array of the first `15` records matching the filters.
+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.
# restore Method - Service Factory Reference
@@ -31093,142 +31476,6 @@ By default, all of the record's properties are retrieved. To select specific one
The method returns the record as an object.
-# 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.
-
-
# softDelete Method - Service Factory Reference
This method soft deletes one or more records of the data model.
@@ -31316,6 +31563,46 @@ deletedPosts = {
```
+# delete Method - Service Factory Reference
+
+This method deletes one or more records.
+
+## Delete One Record
+
+```ts
+await postModuleService.deletePosts("123")
+```
+
+To delete one record, pass its ID as a parameter of the method.
+
+***
+
+## Delete Multiple Records
+
+```ts
+await postModuleService.deletePosts([
+ "123",
+ "321",
+])
+```
+
+To delete multiple records, pass an array of IDs as a parameter of the method.
+
+***
+
+## Delete Records Matching Filters
+
+```ts
+await postModuleService.deletePosts({
+ name: "My Post",
+})
+```
+
+To 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).
+
+
# update Method - Service Factory Reference
This method updates one or more records of the data model.
@@ -31439,149 +31726,6 @@ Learn more about accepted filters in [this documentation](https://docs.medusajs.
The method returns an array of objects of updated records.
-# 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.
-
-This guide provides examples of using filters.
-
-The `list` method is used in the example snippets of this reference, but you can use the same filtering mechanism with any method that accepts filters.
-
-***
-
-## Match Exact Value
-
-```ts
-const posts = await postModuleService.listPosts({
- name: "My Post 2",
-})
-```
-
-If you pass a property with its value, only records whose properties exactly match the value are selected.
-
-In the example above, only posts having the name `My Post 2` are retrieved.
-
-***
-
-## Match Multiple Values
-
-```ts
-const posts = await postModuleService.listPosts({
- views: [
- 50,
- 100,
- ],
-})
-```
-
-To find records with a property matching multiple values, pass an array of those values as the property's value in the filter.
-
-In the example above, only posts having either `50` or `100` views are retrieved.
-
-***
-
-## Don't Match Values
-
-```ts
-const posts = await postModuleService.listPosts({
- name: {
- $nin: [
- "My Post",
- ],
- },
-})
-```
-
-To find records with a property that doesn't match one or more values, pass an object with a `$nin` property. Its value is an array of multiple values that a record's property shouldn't match.
-
-In the example above, only posts that don't have the name `My Post` are retrieved.
-
-***
-
-## Match Text Like Value
-
-This filter only applies to text-like properties, including `id` and `enum` properties.
-
-```ts
-const posts = await postModuleService.listPosts({
- name: {
- $like: "My%",
- },
-})
-```
-
-To perform a `like` filter on a record's property, set the property's value to an object with a `$like` property. Its value is the string to use when applying the `like` filter.
-
-The example above matches all posts whose name starts with `My`.
-
-***
-
-## Apply Range Filters
-
-This filter only applies to the `number` and `dateTime` properties.
-
-```ts
-const posts = await postModuleService.listPosts({
- published_at: {
- $lt: new Date(),
- },
-})
-```
-
-To filter a record's property to be within a range, set the property's value to an object with any of the following properties:
-
-1. `$lt`: The property's value must be less than the supplied value.
-2. `$lte`: The property's value must be less than or equal to the supplied value.
-3. `$gt`: The property's value must be greater than the supplied value.
-4. `$gte`: The property's value must be greater than or equal the supplied value.
-
-In the example above, only posts whose `published_at` property is before the current date and time are retrieved.
-
-### Example: Retrieve Posts Published Today
-
-```ts
-const startToday = new Date()
-startToday.setHours(0, 0, 0, 0)
-
-const endToday = new Date()
-endToday.setHours(23, 59, 59, 59)
-
-const posts = await postModuleService.listPosts({
- published_at: {
- $gte: startToday,
- $lte: endToday,
- },
-})
-```
-
-The `dateTime` property also stores the time. So, when matching for an exact day, you must set a range filter to be between the beginning and end of the day.
-
-In this example, you retrieve the current date twice: once to set its time to `00:00:00`, and another to set its time `23:59:59`. Then, you retrieve posts whose `published_at` property is between `00:00:00` and `23:59:59` of today.
-
-***
-
-## Apply Or Condition
-
-```ts
-const posts = await postModuleService.listPosts({
- $or: [
- {
- name: "My Post",
- },
- {
- published_at: {
- $lt: new Date(),
- },
- },
- ],
-})
-```
-
-To use an `or` condition, pass to the filter object the `$or` property, whose value is an array of filters.
-
-In the example above, posts whose name is `My Post` or their `published_at` date is less than the current date and time are retrieved.
-
-
Just Getting Started?
@@ -32023,6 +32167,125 @@ How to install and setup Medusa UI.
+# Medusa Admin Extension
+
+How to install and use Medusa UI for building Admin extensions.
+
+## Installation
+
+***
+
+The `@medusajs/ui` package is a already installed as a dependency of the `@medusajs/admin` package. Due to this you can simply import the package and use it in your local Admin extensions.
+
+If you are building a Admin extension as part of a Medusa plugin, you can install the package as a dependency of your plugin.
+
+```bash
+npm install @medusajs/ui
+```
+
+## Configuration
+
+***
+
+The configuration of the UI package is handled by the `@medusajs/admin` package. Therefore, you do not need to any additional configuration to use the UI package in your Admin extensions.
+
+
+# Standalone Project
+
+How to install and use Medusa UI in a standalone project.
+
+## Installation
+
+***
+
+Medusa UI is a React UI library and while it's intended for usage within Medusa projects, it can also be used in any React project.
+
+### Install Medusa UI
+
+Install the React UI library with the following command:
+
+```bash
+npm install @medusajs/ui
+```
+
+### Configuring Tailwind CSS
+
+The components are styled using Tailwind CSS, and in order to use them, you will need to install Tailwind CSS in your project as well.
+For more information on how to install Tailwind CSS, please refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs/installation).
+
+All of the classes used for Medusa UI are shipped as a Tailwind CSS customization.
+You can install it with the following command:
+
+```bash
+npm install @medusajs/ui-preset
+```
+
+After you have installed Tailwind CSS and the Medusa UI preset, you need to add the following to your `tailwind.config.js`file:
+
+```tsx
+module.exports = {
+ presets: [require("@medusajs/ui-preset")],
+ // ...
+}
+```
+
+In order for the styles to be applied correctly to the components, you will also need to ensure that
+`@medusajs/ui` is included in the content field of your `tailwind.config.js` file:
+
+```tsx
+module.exports = {
+ content: [
+ // ...
+ "./node_modules/@medusajs/ui/dist/**/*.{js,jsx,ts,tsx}",
+ ],
+ // ...
+}
+```
+
+If you are working within a monorepo, you may need to add the path to the `@medusajs/ui` package in your `tailwind.config.js` like so:
+
+```tsx
+const path = require("path")
+
+const uiPath = path.resolve(
+ require.resolve("@medusajs/ui"),
+ "../..",
+ "\*_/_.{js,jsx,ts,tsx}"
+)
+
+module.exports = {
+ content: [
+ // ...
+ uiPath,
+ ],
+ // ...
+}
+
+```
+
+## Start building
+
+***
+
+You are now ready to start building your application with Medusa UI. You can import the components like so:
+
+```tsx
+import { Button, Drawer } from "@medusajs/ui"
+```
+
+## Updating UI Packages
+
+***
+
+Medusa's design-system packages, including `@medusajs/ui`, `@medusajs/ui-preset`, and `@medusajs/ui-icons`, are versioned independently. However, they're still part of the latest Medusa release. So, you can browse the [release notes](https://github.com/medusajs/medusa/releases) to see if there are any breaking changes to these packages.
+
+To update these packages, update their version in your `package.json` file and re-install dependencies. For example:
+
+```bash
+npm install @medusajs/ui
+```
+
+
# Alert
A component for displaying important messages.
@@ -38418,125 +38681,6 @@ If you're using the `Tooltip` component in a project other than the Medusa Admin
- disableHoverableContent: (boolean) When \`true\`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger.
-# Medusa Admin Extension
-
-How to install and use Medusa UI for building Admin extensions.
-
-## Installation
-
-***
-
-The `@medusajs/ui` package is a already installed as a dependency of the `@medusajs/admin` package. Due to this you can simply import the package and use it in your local Admin extensions.
-
-If you are building a Admin extension as part of a Medusa plugin, you can install the package as a dependency of your plugin.
-
-```bash
-npm install @medusajs/ui
-```
-
-## Configuration
-
-***
-
-The configuration of the UI package is handled by the `@medusajs/admin` package. Therefore, you do not need to any additional configuration to use the UI package in your Admin extensions.
-
-
-# Standalone Project
-
-How to install and use Medusa UI in a standalone project.
-
-## Installation
-
-***
-
-Medusa UI is a React UI library and while it's intended for usage within Medusa projects, it can also be used in any React project.
-
-### Install Medusa UI
-
-Install the React UI library with the following command:
-
-```bash
-npm install @medusajs/ui
-```
-
-### Configuring Tailwind CSS
-
-The components are styled using Tailwind CSS, and in order to use them, you will need to install Tailwind CSS in your project as well.
-For more information on how to install Tailwind CSS, please refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs/installation).
-
-All of the classes used for Medusa UI are shipped as a Tailwind CSS customization.
-You can install it with the following command:
-
-```bash
-npm install @medusajs/ui-preset
-```
-
-After you have installed Tailwind CSS and the Medusa UI preset, you need to add the following to your `tailwind.config.js`file:
-
-```tsx
-module.exports = {
- presets: [require("@medusajs/ui-preset")],
- // ...
-}
-```
-
-In order for the styles to be applied correctly to the components, you will also need to ensure that
-`@medusajs/ui` is included in the content field of your `tailwind.config.js` file:
-
-```tsx
-module.exports = {
- content: [
- // ...
- "./node_modules/@medusajs/ui/dist/**/*.{js,jsx,ts,tsx}",
- ],
- // ...
-}
-```
-
-If you are working within a monorepo, you may need to add the path to the `@medusajs/ui` package in your `tailwind.config.js` like so:
-
-```tsx
-const path = require("path")
-
-const uiPath = path.resolve(
- require.resolve("@medusajs/ui"),
- "../..",
- "\*_/_.{js,jsx,ts,tsx}"
-)
-
-module.exports = {
- content: [
- // ...
- uiPath,
- ],
- // ...
-}
-
-```
-
-## Start building
-
-***
-
-You are now ready to start building your application with Medusa UI. You can import the components like so:
-
-```tsx
-import { Button, Drawer } from "@medusajs/ui"
-```
-
-## Updating UI Packages
-
-***
-
-Medusa's design-system packages, including `@medusajs/ui`, `@medusajs/ui-preset`, and `@medusajs/ui-icons`, are versioned independently. However, they're still part of the latest Medusa release. So, you can browse the [release notes](https://github.com/medusajs/medusa/releases) to see if there are any breaking changes to these packages.
-
-To update these packages, update their version in your `package.json` file and re-install dependencies. For example:
-
-```bash
-npm install @medusajs/ui
-```
-
-
# clx
Utility function for working with classNames.
diff --git a/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx b/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
index 7edc4f7328f07..75bcdf2a3a1c4 100644
--- a/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
+++ b/www/apps/resources/app/commerce-modules/auth/authentication-route/page.mdx
@@ -339,8 +339,9 @@ If the authentication is successful, the request returns a `201` response code.
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?token=123
+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"
@@ -360,9 +361,15 @@ 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`.
-#### Query Parameters
+#### Pass Token in Authorization Header
-The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route).
+
+
+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
diff --git a/www/apps/resources/app/storefront-development/customers/reset-password/page.mdx b/www/apps/resources/app/storefront-development/customers/reset-password/page.mdx
index b7d517c9aef4f..8c51bbee29972 100644
--- a/www/apps/resources/app/storefront-development/customers/reset-password/page.mdx
+++ b/www/apps/resources/app/storefront-development/customers/reset-password/page.mdx
@@ -167,8 +167,8 @@ export const resetPasswordFetchHighlights = [
["3", "email", "Receive the email from a query parameter."],
["9", "password", "Assuming the password is retrieved from an input field."],
["14", "fetch", "Send a request to update the customer's password."],
- ["14", "token", "Pass the token as a query parameter."],
- ["20", "body", "Pass the email and password in the request body."]
+ ["19", "token", "Pass the token in the Authorization header."],
+ ["21", "body", "Pass the email and password in the request body."]
]
```ts highlights={resetPasswordFetchHighlights}
@@ -185,11 +185,12 @@ export const resetPasswordFetchHighlights = [
return
}
- fetch(`http://localhost:9000/auth/customer/emailpass/update?token=${token}`, {
+ fetch(`http://localhost:9000/auth/customer/emailpass/update`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
email,
@@ -210,8 +211,8 @@ export const resetPasswordHighlights = [
["18", "token", "Receive the token from a query parameter."],
["21", "email", "Receive the email from a query parameter."],
["35", "fetch", "Send a request to update the customer's password."],
- ["35", "token", "Pass the token as a query parameter."],
- ["41", "body", "Pass the email and password in the request body."]
+ ["40", "token", "Pass the token in the Authorization header."],
+ ["42", "body", "Pass the email and password in the request body."]
]
```tsx highlights={resetPasswordHighlights}
@@ -249,11 +250,12 @@ export const resetPasswordHighlights = [
}
setLoading(true)
- fetch(`http://localhost:9000/auth/customer/emailpass/update?token=${token}`, {
+ fetch(`http://localhost:9000/auth/customer/emailpass/update`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
email,
@@ -289,4 +291,10 @@ export const resetPasswordHighlights = [
In this example, you receive the `token` and `email` from the page's query parameters.
-Then, when the form that has the password field is submitted, you send a request to the `http://localhost:9000/auth/customer/emailpass/update` API route. You pass it the token as a query parameter, and the email and password in the request body.
+Then, when the form that has the password field is submitted, you send a request to the `http://localhost:9000/auth/customer/emailpass/update` API route. You pass it the token as in the Authorization header as a bearer token, and the email and password in the request body.
+
+
+
+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.
+
+
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index b6912d767826f..1f5903e597744 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -1,7 +1,7 @@
export const generatedEditDates = {
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2025-01-13T11:31:35.361Z",
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z",
- "app/commerce-modules/auth/authentication-route/page.mdx": "2025-01-13T11:31:17.572Z",
+ "app/commerce-modules/auth/authentication-route/page.mdx": "2025-03-04T09:13:45.919Z",
"app/commerce-modules/auth/examples/page.mdx": "2024-10-15T15:02:13.794Z",
"app/commerce-modules/auth/module-options/page.mdx": "2025-01-07T12:47:35.073Z",
"app/commerce-modules/auth/page.mdx": "2025-01-09T13:41:05.476Z",
@@ -2108,7 +2108,7 @@ export const generatedEditDates = {
"app/admin-components/layouts/two-column/page.mdx": "2024-10-07T11:16:10.092Z",
"app/admin-components/components/forms/page.mdx": "2024-10-09T12:48:04.229Z",
"app/commerce-modules/auth/reset-password/page.mdx": "2025-02-26T11:18:00.391Z",
- "app/storefront-development/customers/reset-password/page.mdx": "2024-12-19T16:32:00.724Z",
+ "app/storefront-development/customers/reset-password/page.mdx": "2025-03-04T09:15:25.662Z",
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2025-01-06T11:19:22.450Z",
"app/commerce-modules/cart/extend/page.mdx": "2024-12-25T12:48:59.149Z",
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.593Z",