diff --git a/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx b/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
index 9ea97c10cd..bbfd402a11 100644
--- a/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
+++ b/www/apps/book/app/learn/fundamentals/plugins/create/page.mdx
@@ -50,6 +50,8 @@ After the installation is done, the plugin structure will look like this:
## 2. Prepare Plugin
+### Package Name
+
Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application.
For example:
@@ -61,6 +63,8 @@ For example:
}
```
+### Package Keywords
+
In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
```json title="package.json"
@@ -73,6 +77,42 @@ In addition, make sure that the `keywords` field in `package.json` includes the
}
```
+### Package Dependencies
+
+
+
+Your plugin project will already have the dependencies mentioned in this section. If you haven't made any changes to the dependencies, you can skip this section.
+
+
+
+In the `package.json` file you must have the Medusa dependencies as `devDependencies` and `peerDependencies`. In addition, you must have `@swc/core` as a `devDependency`, as it's used by the plugin CLI tools.
+
+For example, assuming `2.5.0` is the latest Medusa version:
+
+```json title="package.json"
+{
+ "devDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/ui": "4.0.4",
+ "@medusajs/icons": "2.5.0",
+ "@swc/core": "1.5.7",
+ },
+ "peerDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/ui": "4.0.3",
+ "@medusajs/icons": "2.5.0",
+ }
+}
+```
+
---
## 3. Publish Plugin Locally for Development and Testing
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index 3930b458d2..07251de07f 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -111,7 +111,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/module-links/link/page.mdx": "2025-01-06T09:27:25.604Z",
"app/learn/conventions/ts-aliases/page.mdx": "2025-01-23T15:01:15.403Z",
"app/learn/fundamentals/workflows/store-executions/page.mdx": "2025-02-11T15:56:03.835Z",
- "app/learn/fundamentals/plugins/create/page.mdx": "2025-02-07T10:26:53.059Z",
+ "app/learn/fundamentals/plugins/create/page.mdx": "2025-02-12T15:48:25.439Z",
"app/learn/fundamentals/plugins/page.mdx": "2025-01-22T10:14:10.433Z",
"app/learn/customization/reuse-customizations/page.mdx": "2025-01-22T10:01:57.665Z",
"app/learn/update/page.mdx": "2025-01-27T08:45:19.030Z",
diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt
index 60a8adefb6..890716ec5f 100644
--- a/www/apps/book/public/llms-full.txt
+++ b/www/apps/book/public/llms-full.txt
@@ -130,6 +130,16 @@ The next chapter covers how you generally deploy the production build.
You can also refer to the [deployment how-to guides](https://docs.medusajs.com/resources/deployment/index.html.md) for platform-specific how-to guides.
+# Debugging and Testing
+
+In the next chapters, you’ll learn about the tools Medusa provides for testing and debugging your Medusa application.
+
+By the end of this chapter, you’ll learn:
+
+- How to use Medusa's `@medusajs/test-utils` test to write integration tests.
+- How to use Medusa’s `Logger` utility to log messages.
+
+
# Medusa Deployment Overview
In this chapter, you’ll learn the general approach to deploying the Medusa application.
@@ -446,14 +456,418 @@ npm install
```
-# Debugging and Testing
+# Configure Instrumentation
-In the next chapters, you’ll learn about the tools Medusa provides for testing and debugging your Medusa application.
+In this chapter, you'll learn about observability in Medusa and how to configure instrumentation with OpenTelemetry.
-By the end of this chapter, you’ll learn:
+## Observability with OpenTelemtry
-- How to use Medusa's `@medusajs/test-utils` test to write integration tests.
-- How to use Medusa’s `Logger` utility to log messages.
+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.
+
+
+# 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!")
+}
+
+export const config = {
+ name: "test-logger",
+ // execute every minute
+ schedule: "* * * * *",
+}
+```
+
+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:
+
+```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.
+
+***
+
+## 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:
+
+```bash
+LOG_FILE=all.log
+```
+
+Your logs are now saved in the `all.log` file at the root of your Medusa application.
+
+The environment variable must be set as a system environment variable and not in `.env`.
+
+***
+
+## 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:
+
+- `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.
+
+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.
+
+
+# Using TypeScript Aliases
+
+By default, Medusa doesn't support TypeScript aliases in production.
+
+If you prefer using TypeScript aliases, install following development dependencies:
+
+```bash npm2yarn
+npm install --save-dev tsc-alias rimraf
+```
+
+Where `tsc-alias` is a package that resolves TypeScript aliases, and `rimraf` is a package that removes files and directories.
+
+Then, add a new `resolve:aliases` script to your `package.json` and update the `build` script:
+
+```json title="package.json"
+{
+ "scripts": {
+ // other scripts...
+ "resolve:aliases": "tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && tsc-alias -p tsconfig.resolved.json && rimraf tsconfig.resolved.json",
+ "build": "npm run resolve:aliases && medusa build"
+ }
+}
+```
+
+You can now use TypeScript aliases in your Medusa application. For example, add the following in `tsconfig.json`:
+
+```json title="tsconfig.json"
+{
+ "compilerOptions": {
+ // ...
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
+```
+
+Now, you can import modules, for example, using TypeScript aliases:
+
+```ts
+import { BrandModuleService } from "@/modules/brand/service"
+```
+
+
+# Admin Development
+
+In the next chapters, you'll learn more about possible admin customizations.
+
+You can customize the admin dashboard by:
+
+- Adding new sections to existing pages using Widgets.
+- Adding new pages using UI Routes.
+
+***
+
+## Medusa UI Package
+
+Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design.
+
+Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components.
+
+***
+
+## 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.
+
+
+# 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
+
+Writing tests with `@medusajs/test-utils`'s tools requires installing and configuring Jest in your project.
+
+Run the following command to install the required Jest dependencies:
+
+```bash npm2yarn
+npm install --save-dev jest @types/jest @swc/jest
+```
+
+Then, create the file `jest.config.js` with the following content:
+
+```js title="jest.config.js"
+const { loadEnv } = require("@medusajs/framework/utils")
+loadEnv("test", process.cwd())
+
+module.exports = {
+ transform: {
+ "^.+\\.[jt]s$": [
+ "@swc/jest",
+ {
+ jsc: {
+ parser: { syntax: "typescript", decorators: true },
+ },
+ },
+ ],
+ },
+ testEnvironment: "node",
+ moduleFileExtensions: ["js", "ts", "json"],
+ modulePathIgnorePatterns: ["dist/"],
+ setupFiles: ["./integration-tests/setup.js"],
+}
+
+if (process.env.TEST_TYPE === "integration:http") {
+ module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"]
+} else if (process.env.TEST_TYPE === "integration:modules") {
+ module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"]
+} else if (process.env.TEST_TYPE === "unit") {
+ module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"]
+}
+```
+
+Next, create the `integration-tests/setup.js` file with the following content:
+
+```js title="integration-tests/setup.js"
+const { MetadataStorage } = require("@mikro-orm/core")
+
+MetadataStorage.clear()
+```
+
+***
+
+## Add Test Commands
+
+Finally, add the following scripts to `package.json`:
+
+```json title="package.json"
+"scripts": {
+ // ...
+ "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
+ "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit",
+ "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit"
+},
+```
+
+You now have two commands:
+
+- `test:integration:http` to run integration tests (for example, for API routes and workflows) available under the `integration-tests/http` directory.
+- `test:integration:modules` to run integration tests for modules available in any `__tests__` directory under `src/modules`.
+- `test:unit` to run unit tests in any `__tests__` directory under the `src` directory.
+
+Medusa's Testing Framework works for integration tests only. You can write unit tests using Jest.
+
+***
+
+## Test Tools and Writing Tests
+
+The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests.
# General Medusa Application Deployment Guide
@@ -757,50 +1171,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.
-# Using TypeScript Aliases
-
-By default, Medusa doesn't support TypeScript aliases in production.
-
-If you prefer using TypeScript aliases, install following development dependencies:
-
-```bash npm2yarn
-npm install --save-dev tsc-alias rimraf
-```
-
-Where `tsc-alias` is a package that resolves TypeScript aliases, and `rimraf` is a package that removes files and directories.
-
-Then, add a new `resolve:aliases` script to your `package.json` and update the `build` script:
-
-```json title="package.json"
-{
- "scripts": {
- // other scripts...
- "resolve:aliases": "tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && tsc-alias -p tsconfig.resolved.json && rimraf tsconfig.resolved.json",
- "build": "npm run resolve:aliases && medusa build"
- }
-}
-```
-
-You can now use TypeScript aliases in your Medusa application. For example, add the following in `tsconfig.json`:
-
-```json title="tsconfig.json"
-{
- "compilerOptions": {
- // ...
- "paths": {
- "@/*": ["./src/*"]
- }
- }
-}
-```
-
-Now, you can import modules, for example, using TypeScript aliases:
-
-```ts
-import { BrandModuleService } from "@/modules/brand/service"
-```
-
-
# API Routes
In this chapter, you’ll learn what API Routes are and how to create them.
@@ -860,89 +1230,6 @@ curl http://localhost:9000/hello-world
You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
-# Custom CLI Scripts
-
-In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool.
-
-## 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
-```
-
-
-# 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.
-
-
# Environment Variables
In this chapter, you'll learn how environment variables are loaded in Medusa.
@@ -1022,6 +1309,76 @@ You should opt for setting configurations in `medusa-config.ts` where possible.
||Whether to disable analytics data collection. Learn more in ||
+# 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
+```
+
+
# Events and Subscribers
In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers.
@@ -1124,6 +1481,169 @@ Medusa provides two Event Modules out of the box:
Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md).
+# 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.
+
+
+# Medusa Container
+
+In this chapter, you’ll learn about the Medusa container and how to use it.
+
+## What is the Medusa Container?
+
+The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
+
+In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
+
+Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
+
+For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
+
+```ts highlights={highlights}
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const query = req.scope.resolve("query")
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ res.json({
+ products,
+ })
+}
+```
+
+The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
+
+You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+***
+
+## List of Resources Registered in the Medusa Container
+
+Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
+
+***
+
+## How to Resolve From the Medusa Container
+
+This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
+
+### Subscriber
+
+A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={subscriberHighlights}
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function productCreateHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: data.id,
+ },
+ })
+
+ console.log(`You created a product with the title ${products[0].title}`)
+}
+
+export const config: SubscriberConfig = {
+ event: `product.created`,
+}
+```
+
+### Scheduled Job
+
+A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={scheduledJobHighlights}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ console.log(
+ `You have ${products.length} matching your filters.`
+ )
+}
+
+export const config = {
+ name: "every-minute-message",
+ // execute every minute
+ schedule: "* * * * *",
+}
+```
+
+### Workflow Step
+
+A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+
+```ts highlights={workflowStepsHighlight}
+import {
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
+
+ const { data: products } = await query.graph({
+ entity: "product",
+ fields: ["*"],
+ filters: {
+ id: "prod_123",
+ },
+ })
+
+ return new StepResponse(products)
+})
+```
+
+### Module Services and Loaders
+
+A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
+
+Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
+
+
# Module Link
In this chapter, you’ll learn what a module link is.
@@ -1248,50 +1768,6 @@ export default defineLink(
In this example, when a product is deleted, its linked `myCustom` record is also deleted.
-# Plugins
-
-In this chapter, you'll learn what a plugin is in Medusa.
-
-Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
-
-## What is a Plugin?
-
-A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. The supported customizations are [Modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), [API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md), [Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), [Subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), [Scheduled Jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md), and [Admin Extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
-
-Plugins allow you to reuse your Medusa customizations across multiple projects or share them with the community. They can be published to npm and installed in any Medusa project.
-
-
-
-Learn how to create a wishlist plugin in [this guide](https://docs.medusajs.com/resources/plugins/guides/wishlist/index.html.md).
-
-***
-
-## Plugin vs Module
-
-A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) is an isolated package related to a single domain or functionality, such as product reviews or integrating a Content Management System. A module can't access any resources in the Medusa application that are outside its codebase.
-
-A plugin, on the other hand, can contain multiple Medusa customizations, including modules. Your plugin can define a module, then build flows around it.
-
-For example, in a plugin, you can define a module that integrates a third-party service, then add a workflow that uses the module when a certain event occurs to sync data to that service.
-
-- You want to reuse your Medusa customizations across multiple projects.
-- You want to share your Medusa customizations with the community.
-
-- You want to build a custom feature related to a single domain or integrate a third-party service. Instead, use a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). You can wrap that module in a plugin if it's used in other customizations, such as if it has a module link or it's used in a workflow.
-
-***
-
-## How to Create a Plugin?
-
-The next chapter explains how you can create and publish a plugin.
-
-***
-
-## Plugin Guides and Resources
-
-For more resources and guides related to plugins, refer to the [Resources documentation](https://docs.medusajs.com/resources/plugins/index.html.md).
-
-
# Modules
In this chapter, you’ll learn about modules and how to create them.
@@ -1592,154 +2068,48 @@ 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 Container
+# Plugins
-In this chapter, you’ll learn about the Medusa container and how to use it.
+In this chapter, you'll learn what a plugin is in Medusa.
-## What is the Medusa Container?
+Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
-The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations.
+## What is a Plugin?
-In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies.
+A plugin is a package of reusable Medusa customizations that you can install in any Medusa application. The supported customizations are [Modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), [API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [Workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md), [Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), [Subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), [Scheduled Jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md), and [Admin Extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
-Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it.
+Plugins allow you to reuse your Medusa customizations across multiple projects or share them with the community. They can be published to npm and installed in any Medusa project.
-For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it:
+
-```ts highlights={highlights}
-import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const query = req.scope.resolve("query")
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- res.json({
- products,
- })
-}
-```
-
-The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with.
-
-You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+Learn how to create a wishlist plugin in [this guide](https://docs.medusajs.com/resources/plugins/guides/wishlist/index.html.md).
***
-## List of Resources Registered in the Medusa Container
+## Plugin vs Module
-Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md)
+A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) is an isolated package related to a single domain or functionality, such as product reviews or integrating a Content Management System. A module can't access any resources in the Medusa application that are outside its codebase.
+
+A plugin, on the other hand, can contain multiple Medusa customizations, including modules. Your plugin can define a module, then build flows around it.
+
+For example, in a plugin, you can define a module that integrates a third-party service, then add a workflow that uses the module when a certain event occurs to sync data to that service.
+
+- You want to reuse your Medusa customizations across multiple projects.
+- You want to share your Medusa customizations with the community.
+
+- You want to build a custom feature related to a single domain or integrate a third-party service. Instead, use a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). You can wrap that module in a plugin if it's used in other customizations, such as if it has a module link or it's used in a workflow.
***
-## How to Resolve From the Medusa Container
+## How to Create a Plugin?
-This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above.
+The next chapter explains how you can create and publish a plugin.
-### Subscriber
+***
-A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
+## Plugin Guides and Resources
-```ts highlights={subscriberHighlights}
-import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function productCreateHandler({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: data.id,
- },
- })
-
- console.log(`You created a product with the title ${products[0].title}`)
-}
-
-export const config: SubscriberConfig = {
- event: `product.created`,
-}
-```
-
-### Scheduled Job
-
-A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={scheduledJobHighlights}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- console.log(
- `You have ${products.length} matching your filters.`
- )
-}
-
-export const config = {
- name: "every-minute-message",
- // execute every minute
- schedule: "* * * * *",
-}
-```
-
-### Workflow Step
-
-A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key:
-
-```ts highlights={workflowStepsHighlight}
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const query = container.resolve(ContainerRegistrationKeys.QUERY)
-
- const { data: products } = await query.graph({
- entity: "product",
- fields: ["*"],
- filters: {
- id: "prod_123",
- },
- })
-
- return new StepResponse(products)
-})
-```
-
-### Module Services and Loaders
-
-A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container.
-
-Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md).
+For more resources and guides related to plugins, refer to the [Resources documentation](https://docs.medusajs.com/resources/plugins/index.html.md).
# Scheduled Jobs
@@ -1836,30 +2206,6 @@ In the scheduled job function, you execute the `syncProductToErpWorkflow` by inv
The next time you start the Medusa application, it will run this job every day at midnight.
-# Admin Development
-
-In the next chapters, you'll learn more about possible admin customizations.
-
-You can customize the admin dashboard by:
-
-- Adding new sections to existing pages using Widgets.
-- Adding new pages using UI Routes.
-
-***
-
-## Medusa UI Package
-
-Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design.
-
-Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components.
-
-***
-
-## 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.
-
-
# Workflows
In this chapter, you’ll learn about workflows and how to define and execute them.
@@ -2114,6 +2460,64 @@ You can now execute this workflow in a custom API route, scheduled job, or subsc
Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows.
+# Medusa's Architecture
+
+In this chapter, you'll learn about the architectural layers in Medusa.
+
+## 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.
+
+
+
+
# Next.js Starter Storefront
The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
@@ -2180,410 +2584,6 @@ The Next.js Starter is compatible with some Medusa integrations out-of-the-box,
Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details.
-# 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.
-
-
-
-
-# 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!")
-}
-
-export const config = {
- name: "test-logger",
- // execute every minute
- schedule: "* * * * *",
-}
-```
-
-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:
-
-```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.
-
-***
-
-## 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:
-
-```bash
-LOG_FILE=all.log
-```
-
-Your logs are now saved in the `all.log` file at the root of your Medusa application.
-
-The environment variable must be set as a system environment variable and not in `.env`.
-
-***
-
-## 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:
-
-- `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.
-
-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.
-
-
-# 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
-
-Writing tests with `@medusajs/test-utils`'s tools requires installing and configuring Jest in your project.
-
-Run the following command to install the required Jest dependencies:
-
-```bash npm2yarn
-npm install --save-dev jest @types/jest @swc/jest
-```
-
-Then, create the file `jest.config.js` with the following content:
-
-```js title="jest.config.js"
-const { loadEnv } = require("@medusajs/framework/utils")
-loadEnv("test", process.cwd())
-
-module.exports = {
- transform: {
- "^.+\\.[jt]s$": [
- "@swc/jest",
- {
- jsc: {
- parser: { syntax: "typescript", decorators: true },
- },
- },
- ],
- },
- testEnvironment: "node",
- moduleFileExtensions: ["js", "ts", "json"],
- modulePathIgnorePatterns: ["dist/"],
- setupFiles: ["./integration-tests/setup.js"],
-}
-
-if (process.env.TEST_TYPE === "integration:http") {
- module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"]
-} else if (process.env.TEST_TYPE === "integration:modules") {
- module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"]
-} else if (process.env.TEST_TYPE === "unit") {
- module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"]
-}
-```
-
-Next, create the `integration-tests/setup.js` file with the following content:
-
-```js title="integration-tests/setup.js"
-const { MetadataStorage } = require("@mikro-orm/core")
-
-MetadataStorage.clear()
-```
-
-***
-
-## Add Test Commands
-
-Finally, add the following scripts to `package.json`:
-
-```json title="package.json"
-"scripts": {
- // ...
- "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
- "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit",
- "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit"
-},
-```
-
-You now have two commands:
-
-- `test:integration:http` to run integration tests (for example, for API routes and workflows) available under the `integration-tests/http` directory.
-- `test:integration:modules` to run integration tests for modules available in any `__tests__` directory under `src/modules`.
-- `test:unit` to run unit tests in any `__tests__` directory under the `src` directory.
-
-Medusa's Testing Framework works for integration tests only. You can write unit tests using Jest.
-
-***
-
-## Test Tools and Writing Tests
-
-The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests.
-
-
# Build Custom Features
In the upcoming chapters, you'll follow step-by-step guides to build custom features in Medusa. These guides gradually introduce Medusa's concepts to help you understand what they are and how to use them.
@@ -2630,27 +2630,27 @@ In the next chapters, you'll continue with the brands example to:
- Add a new page in the dashboard that shows all brands in the store.
-# Integrate Third-Party Systems
+# Extend Core Commerce Features
-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.
+In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
-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 other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
-In Medusa, you integrate a third-party system by:
+Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
-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.
+- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
+- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
+- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
***
-## Next Chapters: Sync Brands Example
+## Next Chapters: Link Brands to Products 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:
+The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
-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.
+- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
+- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
+- Retrieve a product's associated brand's details.
# Customizations Next Steps: Learn the Fundamentals
@@ -2692,6 +2692,983 @@ Medusa provides the tooling to create a plugin package, test it in a local Medus
To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md).
+# Integrate Third-Party Systems
+
+Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails.
+
+Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly.
+
+In Medusa, you integrate a third-party system by:
+
+1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system.
+2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps.
+3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted.
+
+***
+
+## Next Chapters: Sync Brands Example
+
+In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will:
+
+1. Integrate a dummy third-party CMS in the Brand Module.
+2. Sync brands to the CMS when a brand is created.
+3. Sync brands from the CMS at a daily schedule.
+
+
+# Admin Development Constraints
+
+This chapter lists some constraints of admin widgets and UI routes.
+
+## Arrow Functions
+
+Widget and UI route components must be created as arrow functions.
+
+```ts highlights={arrowHighlights}
+// Don't
+function ProductWidget() {
+ // ...
+}
+
+// Do
+const ProductWidget = () => {
+ // ...
+}
+```
+
+***
+
+## Widget Zone
+
+A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable.
+
+```ts highlights={zoneHighlights}
+// Don't
+export const config = defineWidgetConfig({
+ zone: `product.details.before`,
+})
+
+// Don't
+const ZONE = "product.details.after"
+export const config = defineWidgetConfig({
+ zone: ZONE,
+})
+
+// Do
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+```
+
+
+# Environment Variables in Admin Customizations
+
+In this chapter, you'll learn how to use environment variables in your admin customizations.
+
+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).
+
+## How to Set Environment Variables
+
+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_`.
+
+For example:
+
+```plain
+VITE_MY_API_KEY=sk_123
+```
+
+***
+
+## How to Use Environment Variables
+
+To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object.
+
+For example:
+
+```tsx highlights={[["8"]]}
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const ProductWidget = () => {
+ return (
+
+
+ API Key: {import.meta.env.VITE_MY_API_KEY}
+
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`.
+
+### Type Error on import.meta.env
+
+If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content:
+
+```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 Development Tips
+
+In this chapter, you'll find some tips for your admin development.
+
+## Send Requests to API Routes
+
+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.
+
+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:
+
+```ts
+import Medusa from "@medusajs/js-sdk"
+
+export const sdk = new Medusa({
+ baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
+ debug: import.meta.env.DEV,
+ auth: {
+ type: "session",
+ },
+})
+```
+
+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).
+
+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:
+
+### Query
+
+```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights}
+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"
+
+const ProductWidget = () => {
+ const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list(),
+ queryKey: ["products"],
+ })
+
+ return (
+
+ {isLoading && Loading...}
+ {data?.products && (
+
+ {data.products.map((product) => (
+
{product.title}
+ ))}
+
+ )}
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.list.before",
+})
+
+export default ProductWidget
+```
+
+### Mutation
+
+```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"
+
+const ProductWidget = ({
+ 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 (
+
+
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+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).
+
+### 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).
+
+***
+
+## Global Variables in Admin Customizations
+
+In your admin customizations, you can use the following global variables:
+
+- `__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`.
+
+***
+
+## Admin Translations
+
+The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions.
+
+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).
+
+
+# Admin UI Routes
+
+In this chapter, you’ll learn how to create a UI route in the admin dashboard.
+
+## What is a UI Route?
+
+The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions.
+
+For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa.
+
+***
+
+## How to Create a UI Route?
+
+### Prerequisites
+
+- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
+
+You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component.
+
+For example, create the file `src/admin/routes/custom/page.tsx` with the following content:
+
+
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ return (
+
+
+ This is my custom route
+
+
+ )
+}
+
+export default CustomPage
+```
+
+You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading.
+
+In the route, 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.
+
+The UI route component must be created as an arrow function.
+
+### Test the UI Route
+
+To test the UI route, start the Medusa application:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page.
+
+***
+
+## Show UI Route in the Sidebar
+
+To add a sidebar item for your custom UI route, export a configuration object in the UI route's file:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ return (
+
+
+ This is my custom route
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom Route",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties:
+
+- `label`: the sidebar item’s label.
+- `icon`: an optional React component used as an icon in the sidebar.
+
+The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md).
+
+### Nested UI Routes
+
+Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations:
+
+
+
+```tsx title="src/admin/routes/custom/nested/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const NestedCustomPage = () => {
+ return (
+
+
+ This is my nested custom route
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Nested Route",
+})
+
+export default NestedCustomPage
+```
+
+This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked.
+
+#### Caveats
+
+Some caveats for nested UI routes in the sidebar:
+
+- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console.
+- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions.
+- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions.
+
+### Route Under Existing Admin Route
+
+You can add a custom UI route under an existing route. For example, you can add a route under the orders route:
+
+```tsx title="src/admin/routes/orders/nested/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const NestedOrdersPage = () => {
+ return (
+
+
+ Nested Orders Page
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Nested Orders",
+ nested: "/orders",
+})
+
+export default NestedOrdersPage
+```
+
+The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item.
+
+***
+
+## Create Settings Page
+
+To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`.
+
+For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`:
+
+
+
+```tsx title="src/admin/routes/settings/custom/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomSettingPage = () => {
+ return (
+
+
+ Custom Setting Page
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+})
+
+export default CustomSettingPage
+```
+
+This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`.
+
+***
+
+## Path Parameters
+
+A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`.
+
+For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content:
+
+
+
+```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]}
+import { useParams } from "react-router-dom"
+import { Container, Heading } from "@medusajs/ui"
+
+const CustomPage = () => {
+ const { id } = useParams()
+
+ return (
+
+
+ Passed ID: {id}
+
+
+ )
+}
+
+export default CustomPage
+```
+
+You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params).
+
+If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page.
+
+***
+
+## 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.
+
+***
+
+## More Routes Customizations
+
+For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
+
+
+# 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",
+})
+
+export default ProductWidget
+```
+
+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.
+
+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.
+
+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.
+
+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"
+
+export async function loader() {
+ // TODO fetch products
+
+ return {
+ products: [],
+ }
+}
+
+const CustomPage = () => {
+ const { products } = useLoaderData() as Awaited>
+
+ return (
+
+
+
+ Products count: {products.length}
+
+
+
+ )
+}
+
+export default CustomPage
+```
+
+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.
+
+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.
+
+### Route Parameters
+
+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`:
+
+```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={loaderParamHighlights}
+import { Container, Heading } from "@medusajs/ui"
+import {
+ useLoaderData,
+ LoaderFunctionArgs,
+} from "react-router-dom"
+
+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.
+
+***
+
+## Other React Router Utilities
+
+### 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 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 (
+
+
+ Product Widget
+
+
+ )
+}
+
+// The widget's configurations
+export const config = defineWidgetConfig({
+ zone: "product.details.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.
+
+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}
+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.
+
+
+# Write Integration Tests
+
+In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## medusaIntegrationTestRunner Utility
+
+The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
+
+For example:
+
+```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ api, getContainer }) => {
+ // TODO write tests...
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
+
+`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
+
+- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
+ - `get`: Send a `GET` request to an API route.
+ - `post`: Send a `POST` request to an API route.
+ - `delete`: Send a `DELETE` request to an API route.
+- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
+
+The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+
+### Jest Timeout
+
+Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+
+```ts title="integration-tests/http/test.spec.ts"
+// in your test's file
+jest.setTimeout(60 * 1000)
+```
+
+***
+
+### Run Tests
+
+Run the following command to run your tests:
+
+```bash npm2yarn
+npm run test:integration
+```
+
+If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
+
+***
+
+## Other Options and Inputs
+
+Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+
+***
+
+## Database Used in Tests
+
+The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
+
+To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
+
+***
+
+## Example Integration Tests
+
+The next chapters provide examples of writing integration tests for API routes and workflows.
+
+
+# Write Tests for Modules
+
+In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service.
+
+### Prerequisites
+
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+
+## moduleIntegrationTestRunner Utility
+
+`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled.
+
+For example, assuming you have a `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)
+```
+
+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 Tests
+
+Run the following command to run your module integration tests:
+
+```bash npm2yarn
+npm run test:integration:modules
+```
+
+If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
+
+***
+
+## Pass Module Options
+
+If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
+
+For example:
+
+```ts
+import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
+import 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).
+
+
# Pass Additional Data to Medusa's API Route
In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.
@@ -2891,161 +3868,6 @@ createProductsWorkflow.hooks.productsCreated(
This updates the products to their original state before adding the brand to their `metadata` property.
-# Handling CORS in API Routes
-
-In this chapter, you’ll learn about the CORS middleware and how to configure it for custom API routes.
-
-## CORS Overview
-
-Cross-Origin Resource Sharing (CORS) allows only configured origins to access your API Routes.
-
-For example, if you allow only origins starting with `http://localhost:7001` to access your Admin API Routes, other origins accessing those routes get a CORS error.
-
-### CORS Configurations
-
-The `storeCors` and `adminCors` properties of Medusa's `http` configuration set the allowed origins for routes starting with `/store` and `/admin` respectively.
-
-These configurations accept a URL pattern to identify allowed origins.
-
-For example:
-
-```js title="medusa-config.ts"
-module.exports = defineConfig({
- projectConfig: {
- http: {
- storeCors: "http://localhost:8000",
- adminCors: "http://localhost:7001",
- // ...
- },
- },
-})
-```
-
-This allows the `http://localhost:7001` origin to access the Admin API Routes, and the `http://localhost:8000` origin to access Store API Routes.
-
-Learn more about the CORS configurations in [this resource guide](https://docs.medusajs.com/resources/references/medusa-config#http/index.html.md).
-
-***
-
-## CORS in Store and Admin Routes
-
-To disable the CORS middleware for a route, export a `CORS` variable in the route file with its value set to `false`.
-
-For example:
-
-```ts title="src/api/store/custom/route.ts" highlights={[["15"]]}
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[GET] Hello world!",
- })
-}
-
-export const CORS = false
-```
-
-This disables the CORS middleware on API Routes at the path `/store/custom`.
-
-***
-
-## CORS in Custom Routes
-
-If you create a route that doesn’t start with `/store` or `/admin`, you must apply the CORS middleware manually. Otherwise, all requests to your API route lead to a CORS error.
-
-You can do that in the exported middlewares configurations in `src/api/middlewares.ts`.
-
-For example:
-
-```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
-import { defineMiddlewares } from "@medusajs/framework/http"
-import type {
- MedusaNextFunction,
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { ConfigModule } from "@medusajs/framework/types"
-import { parseCorsOrigins } from "@medusajs/framework/utils"
-import cors from "cors"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/custom*",
- middlewares: [
- (
- req: MedusaRequest,
- res: MedusaResponse,
- next: MedusaNextFunction
- ) => {
- const configModule: ConfigModule =
- req.scope.resolve("configModule")
-
- return cors({
- origin: parseCorsOrigins(
- configModule.projectConfig.http.storeCors
- ),
- credentials: true,
- })(req, res, next)
- },
- ],
- },
- ],
-})
-```
-
-This retrieves the configurations exported from `medusa-config.ts` and applies the `storeCors` to routes starting with `/custom`.
-
-
-# HTTP Methods
-
-In this chapter, you'll learn about how to add new API routes for each HTTP method.
-
-## HTTP Method Handler
-
-An API route is created for every HTTP method you export a handler function for in a route file.
-
-Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
-
-For example, create the file `src/api/hello-world/route.ts` with the following content:
-
-```ts title="src/api/hello-world/route.ts"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[GET] Hello world!",
- })
-}
-
-export const POST = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- res.json({
- message: "[POST] Hello world!",
- })
-}
-```
-
-This adds two API Routes:
-
-- A `GET` route at `http://localhost:9000/hello-world`.
-- A `POST` route at `http://localhost:9000/hello-world`.
-
-
# 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.
@@ -3153,6 +3975,49 @@ The `errorHandler` property's value is a function that accepts four parameters:
This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
+# HTTP Methods
+
+In this chapter, you'll learn about how to add new API routes for each HTTP method.
+
+## HTTP Method Handler
+
+An API route is created for every HTTP method you export a handler function for in a route file.
+
+Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
+
+For example, create the file `src/api/hello-world/route.ts` with the following content:
+
+```ts title="src/api/hello-world/route.ts"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[GET] Hello world!",
+ })
+}
+
+export const POST = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[POST] Hello world!",
+ })
+}
+```
+
+This adds two API Routes:
+
+- A `GET` route at `http://localhost:9000/hello-world`.
+- A `POST` route at `http://localhost:9000/hello-world`.
+
+
# Middlewares
In this chapter, you’ll learn about middlewares and how to create them.
@@ -3360,151 +4225,6 @@ The Medusa application registers your middlewares first, then registers middlewa
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 wrotes, the authentication middleware defined in the core will still run, leading to your middleware not being effective.
-# 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.
@@ -3676,27 +4396,149 @@ 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.
-# Extend Core Commerce Features
+# API Route Parameters
-In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
+In this chapter, you’ll learn about path, query, and request body parameters.
-In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
+## Path Parameters
-Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
+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]`.
-- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
-- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
-- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
+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.
***
-## Next Chapters: Link Brands to Products Example
+## Query Parameters
-The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to:
+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.
-- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md).
-- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
-- Retrieve a product's associated brand's details.
+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).
# Protected Routes
@@ -4210,6 +5052,519 @@ For example, if you omit the `a` parameter, you'll receive a `400` response code
To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev).
+# Handling CORS in API Routes
+
+In this chapter, you’ll learn about the CORS middleware and how to configure it for custom API routes.
+
+## CORS Overview
+
+Cross-Origin Resource Sharing (CORS) allows only configured origins to access your API Routes.
+
+For example, if you allow only origins starting with `http://localhost:7001` to access your Admin API Routes, other origins accessing those routes get a CORS error.
+
+### CORS Configurations
+
+The `storeCors` and `adminCors` properties of Medusa's `http` configuration set the allowed origins for routes starting with `/store` and `/admin` respectively.
+
+These configurations accept a URL pattern to identify allowed origins.
+
+For example:
+
+```js title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ http: {
+ storeCors: "http://localhost:8000",
+ adminCors: "http://localhost:7001",
+ // ...
+ },
+ },
+})
+```
+
+This allows the `http://localhost:7001` origin to access the Admin API Routes, and the `http://localhost:8000` origin to access Store API Routes.
+
+Learn more about the CORS configurations in [this resource guide](https://docs.medusajs.com/resources/references/medusa-config#http/index.html.md).
+
+***
+
+## CORS in Store and Admin Routes
+
+To disable the CORS middleware for a route, export a `CORS` variable in the route file with its value set to `false`.
+
+For example:
+
+```ts title="src/api/store/custom/route.ts" highlights={[["15"]]}
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ res.json({
+ message: "[GET] Hello world!",
+ })
+}
+
+export const CORS = false
+```
+
+This disables the CORS middleware on API Routes at the path `/store/custom`.
+
+***
+
+## CORS in Custom Routes
+
+If you create a route that doesn’t start with `/store` or `/admin`, you must apply the CORS middleware manually. Otherwise, all requests to your API route lead to a CORS error.
+
+You can do that in the exported middlewares configurations in `src/api/middlewares.ts`.
+
+For example:
+
+```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
+import { defineMiddlewares } from "@medusajs/framework/http"
+import type {
+ MedusaNextFunction,
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { ConfigModule } from "@medusajs/framework/types"
+import { parseCorsOrigins } from "@medusajs/framework/utils"
+import cors from "cors"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/custom*",
+ middlewares: [
+ (
+ req: MedusaRequest,
+ res: MedusaResponse,
+ next: MedusaNextFunction
+ ) => {
+ const configModule: ConfigModule =
+ req.scope.resolve("configModule")
+
+ return cors({
+ origin: parseCorsOrigins(
+ configModule.projectConfig.http.storeCors
+ ),
+ credentials: true,
+ })(req, res, next)
+ },
+ ],
+ },
+ ],
+})
+```
+
+This retrieves the configurations exported from `medusa-config.ts` and applies the `storeCors` to routes starting with `/custom`.
+
+
+# Seed Data with Custom CLI Script
+
+In this chapter, you'll learn how to seed data using a custom CLI script.
+
+## How to Seed Data
+
+To seed dummy data for development or demo purposes, use a custom CLI script.
+
+In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
+
+### Example: Seed Dummy Products
+
+In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
+
+First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
+
+```bash npm2yarn
+npm install --save-dev @faker-js/faker
+```
+
+Then, create the file `src/scripts/demo-products.ts` with the following content:
+
+```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+import { ExecArgs } from "@medusajs/framework/types"
+import { faker } from "@faker-js/faker"
+import {
+ ContainerRegistrationKeys,
+ Modules,
+ ProductStatus,
+} from "@medusajs/framework/utils"
+import {
+ createInventoryLevelsWorkflow,
+ createProductsWorkflow,
+} from "@medusajs/medusa/core-flows"
+
+export default async function seedDummyProducts({
+ container,
+}: ExecArgs) {
+ const salesChannelModuleService = container.resolve(
+ Modules.SALES_CHANNEL
+ )
+ const logger = container.resolve(
+ ContainerRegistrationKeys.LOGGER
+ )
+ const query = container.resolve(
+ ContainerRegistrationKeys.QUERY
+ )
+
+ const defaultSalesChannel = await salesChannelModuleService
+ .listSalesChannels({
+ name: "Default Sales Channel",
+ })
+
+ const sizeOptions = ["S", "M", "L", "XL"]
+ const colorOptions = ["Black", "White"]
+ const currency_code = "eur"
+ const productsNum = 50
+
+ // TODO seed products
+}
+```
+
+So far, in the script, you:
+
+- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
+- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
+- Initialize some default data to use when seeding the products next.
+
+Next, replace the `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+const productsData = new Array(productsNum).fill(0).map((_, index) => {
+ const title = faker.commerce.product() + "_" + index
+ return {
+ title,
+ is_giftcard: true,
+ description: faker.commerce.productDescription(),
+ status: ProductStatus.PUBLISHED,
+ options: [
+ {
+ title: "Size",
+ values: sizeOptions,
+ },
+ {
+ title: "Color",
+ values: colorOptions,
+ },
+ ],
+ images: [
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ {
+ url: faker.image.urlPlaceholder({
+ text: title,
+ }),
+ },
+ ],
+ variants: new Array(10).fill(0).map((_, variantIndex) => ({
+ title: `${title} ${variantIndex}`,
+ sku: `variant-${variantIndex}${index}`,
+ prices: new Array(10).fill(0).map((_, priceIndex) => ({
+ currency_code,
+ amount: 10 * priceIndex,
+ })),
+ options: {
+ Size: sizeOptions[Math.floor(Math.random() * 3)],
+ },
+ })),
+ shipping_profile_id: "sp_123",
+ sales_channels: [
+ {
+ id: defaultSalesChannel[0].id,
+ },
+ ],
+ }
+})
+
+// TODO seed products
+```
+
+You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
+
+Then, replace the new `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+const { result: products } = await createProductsWorkflow(container).run({
+ input: {
+ products: productsData,
+ },
+})
+
+logger.info(`Seeded ${products.length} products.`)
+
+// TODO add inventory levels
+```
+
+You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
+
+Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
+
+```ts title="src/scripts/demo-products.ts"
+logger.info("Seeding inventory levels.")
+
+const { data: stockLocations } = await query.graph({
+ entity: "stock_location",
+ fields: ["id"],
+})
+
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
+ fields: ["id"],
+})
+
+const inventoryLevels = inventoryItems.map((inventoryItem) => ({
+ location_id: stockLocations[0].id,
+ stocked_quantity: 1000000,
+ inventory_item_id: inventoryItem.id,
+}))
+
+await createInventoryLevelsWorkflow(container).run({
+ input: {
+ inventory_levels: inventoryLevels,
+ },
+})
+
+logger.info("Finished seeding inventory levels data.")
+```
+
+You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
+
+Then, you generate inventory levels for each inventory item, associating it with the first stock location.
+
+Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
+
+### Test Script
+
+To test out the script, run the following command in your project's directory:
+
+```bash
+npx medusa exec ./src/scripts/demo-products.ts
+```
+
+This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
+
+
+# Event Data Payload
+
+In this chapter, you'll learn how subscribers receive an event's data payload.
+
+## Access Event's Data Payload
+
+When events are emitted, they’re emitted with a data payload.
+
+The object that the subscriber function receives as a parameter has an `event` property, which is an object holding the event payload in a `data` property with additional context.
+
+For example:
+
+```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
+import type {
+ SubscriberArgs,
+ SubscriberConfig,
+} from "@medusajs/framework"
+
+export default async function productCreateHandler({
+ event,
+}: SubscriberArgs<{ id: string }>) {
+ const productId = event.data.id
+ console.log(`The product ${productId} was created`)
+}
+
+export const config: SubscriberConfig = {
+ event: "product.created",
+}
+```
+
+The `event` object has the following properties:
+
+- data: (\`object\`) The data payload of the event. Its properties are different for each event.
+- name: (string) The name of the triggered event.
+- metadata: (\`object\`) Additional data and context of the emitted event.
+
+This logs the product ID received in the `product.created` event’s data payload to the console.
+
+{/* ---
+
+## List of Events with Data Payload
+
+Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */}
+
+
+# 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({
+ MyCustom,
+}){
+ protected eventBusService_: AbstractEventBusModuleService
+
+ constructor({ event_bus }) {
+ super(...arguments)
+ this.eventBusService_ = event_bus
+ }
+}
+```
+
+### Without Service Factory
+
+```ts title="src/modules/hello/service.ts" highlights={["6"]}
+import { IEventBusService } from "@medusajs/framework/types"
+
+class HelloModuleService {
+ protected eventBusService_: AbstractEventBusModuleService
+
+ constructor({ event_bus }) {
+ this.eventBusService_ = event_bus
+ }
+}
+```
+
+2. Use the event bus service's `emit` method in the service's methods to emit an event:
+
+```ts title="src/modules/hello/service.ts" highlights={serviceHighlights}
+class HelloModuleService {
+ // ...
+ performAction() {
+ // TODO perform action
+
+ this.eventBusService_.emit({
+ name: "custom.event",
+ data: {
+ id: "123",
+ // other data payload
+ },
+ })
+ }
+}
+```
+
+The method accepts an object having the following properties:
+
+- `name`: 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.
+
+3. By default, the Event Module's service isn't injected into your module's container. To add it to the container, pass it in the module's registration object in `medusa-config.ts` in the `dependencies` property:
+
+```ts title="medusa-config.ts" highlights={depsHighlight}
+import { Modules } from "@medusajs/framework/utils"
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/hello",
+ dependencies: [
+ Modules.EVENT_BUS,
+ ],
+ },
+ ],
+})
+```
+
+The `dependencies` property accepts an array of module registration keys. The specified modules' main services are injected into the module's container.
+
+That's how you can resolve it in your module's main service's constructor.
+
+### Test it Out
+
+If you execute the `performAction` method of your service, the event is emitted and you can see it in your application's logs.
+
+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.
@@ -4279,6 +5634,78 @@ export default User
In this example, multiple users can’t have the same email.
+# Add Data Model Check Constraints
+
+In this chapter, you'll learn how to add check constraints to your data model.
+
+## What is a Check Constraint?
+
+A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
+
+For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
+
+***
+
+## How to Set a Check Constraint?
+
+To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
+
+For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
+
+```ts highlights={checks1Highlights}
+import { model } from "@medusajs/framework/utils"
+
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ (columns) => `${columns.price} >= 0`,
+])
+```
+
+The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
+
+The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
+
+You can also pass an object to the `checks` method:
+
+```ts highlights={checks2Highlights}
+import { model } from "@medusajs/framework/utils"
+
+const CustomProduct = model.define("custom_product", {
+ // ...
+ price: model.bigNumber(),
+})
+.checks([
+ {
+ name: "custom_product_price_check",
+ expression: (columns) => `${columns.price} >= 0`,
+ },
+])
+```
+
+The object accepts the following properties:
+
+- `name`: The check constraint's name.
+- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
+
+***
+
+## Apply in Migrations
+
+After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
+
+To generate a migration for the data model's module then reflect it on the database, run the following command:
+
+```bash
+npx medusa db:generate custom_module
+npx medusa db:migrate
+```
+
+The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
+
+
# Data Model Database Index
In this chapter, you’ll learn how to define a database index on a data model.
@@ -4414,78 +5841,6 @@ export default MyCustom
This creates a unique composite index on the `name` and `age` properties.
-# Add Data Model Check Constraints
-
-In this chapter, you'll learn how to add check constraints to your data model.
-
-## What is a Check Constraint?
-
-A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown.
-
-For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value.
-
-***
-
-## How to Set a Check Constraint?
-
-To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model.
-
-For example, to set a check constraint on a `price` property that ensures its value can only be a positive number:
-
-```ts highlights={checks1Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- (columns) => `${columns.price} >= 0`,
-])
-```
-
-The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database.
-
-The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name.
-
-You can also pass an object to the `checks` method:
-
-```ts highlights={checks2Highlights}
-import { model } from "@medusajs/framework/utils"
-
-const CustomProduct = model.define("custom_product", {
- // ...
- price: model.bigNumber(),
-})
-.checks([
- {
- name: "custom_product_price_check",
- expression: (columns) => `${columns.price} >= 0`,
- },
-])
-```
-
-The object accepts the following properties:
-
-- `name`: The check constraint's name.
-- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS).
-
-***
-
-## Apply in Migrations
-
-After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected.
-
-To generate a migration for the data model's module then reflect it on the database, run the following command:
-
-```bash
-npx medusa db:generate custom_module
-npx medusa db:migrate
-```
-
-The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database.
-
-
# Infer Type of Data Model
In this chapter, you'll learn how to infer the type of a data model.
@@ -4526,6 +5881,30 @@ class HelloModuleService extends MedusaService({ MyCustom }) {
```
+# Data Model’s Primary Key
+
+In this chapter, you’ll learn how to configure the primary key of a data model.
+
+## primaryKey Method
+
+To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
+
+For example:
+
+```ts highlights={highlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ id: model.id().primaryKey(),
+ // ...
+})
+
+export default MyCustom
+```
+
+In the example above, the `id` property is defined as the data model's primary key.
+
+
# Manage Relationships
In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
@@ -4755,218 +6134,6 @@ When you create a data model, the following properties are created for you by Me
- `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.
-# Seed Data with Custom CLI Script
-
-In this chapter, you'll learn how to seed data using a custom CLI script.
-
-## How to Seed Data
-
-To seed dummy data for development or demo purposes, use a custom CLI script.
-
-In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data.
-
-### Example: Seed Dummy Products
-
-In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products.
-
-First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script:
-
-```bash npm2yarn
-npm install --save-dev @faker-js/faker
-```
-
-Then, create the file `src/scripts/demo-products.ts` with the following content:
-
-```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import { ExecArgs } from "@medusajs/framework/types"
-import { faker } from "@faker-js/faker"
-import {
- ContainerRegistrationKeys,
- Modules,
- ProductStatus,
-} from "@medusajs/framework/utils"
-import {
- createInventoryLevelsWorkflow,
- createProductsWorkflow,
-} from "@medusajs/medusa/core-flows"
-
-export default async function seedDummyProducts({
- container,
-}: ExecArgs) {
- const salesChannelModuleService = container.resolve(
- Modules.SALES_CHANNEL
- )
- const logger = container.resolve(
- ContainerRegistrationKeys.LOGGER
- )
- const query = container.resolve(
- ContainerRegistrationKeys.QUERY
- )
-
- const defaultSalesChannel = await salesChannelModuleService
- .listSalesChannels({
- name: "Default Sales Channel",
- })
-
- const sizeOptions = ["S", "M", "L", "XL"]
- const colorOptions = ["Black", "White"]
- const currency_code = "eur"
- const productsNum = 50
-
- // TODO seed products
-}
-```
-
-So far, in the script, you:
-
-- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in.
-- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products.
-- Initialize some default data to use when seeding the products next.
-
-Next, replace the `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const productsData = new Array(productsNum).fill(0).map((_, index) => {
- const title = faker.commerce.product() + "_" + index
- return {
- title,
- is_giftcard: true,
- description: faker.commerce.productDescription(),
- status: ProductStatus.PUBLISHED,
- options: [
- {
- title: "Size",
- values: sizeOptions,
- },
- {
- title: "Color",
- values: colorOptions,
- },
- ],
- images: [
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- {
- url: faker.image.urlPlaceholder({
- text: title,
- }),
- },
- ],
- variants: new Array(10).fill(0).map((_, variantIndex) => ({
- title: `${title} ${variantIndex}`,
- sku: `variant-${variantIndex}${index}`,
- prices: new Array(10).fill(0).map((_, priceIndex) => ({
- currency_code,
- amount: 10 * priceIndex,
- })),
- options: {
- Size: sizeOptions[Math.floor(Math.random() * 3)],
- },
- })),
- shipping_profile_id: "sp_123",
- sales_channels: [
- {
- id: defaultSalesChannel[0].id,
- },
- ],
- }
-})
-
-// TODO seed products
-```
-
-You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images.
-
-Then, replace the new `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-const { result: products } = await createProductsWorkflow(container).run({
- input: {
- products: productsData,
- },
-})
-
-logger.info(`Seeded ${products.length} products.`)
-
-// TODO add inventory levels
-```
-
-You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products.
-
-Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following:
-
-```ts title="src/scripts/demo-products.ts"
-logger.info("Seeding inventory levels.")
-
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: ["id"],
-})
-
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: ["id"],
-})
-
-const inventoryLevels = inventoryItems.map((inventoryItem) => ({
- location_id: stockLocations[0].id,
- stocked_quantity: 1000000,
- inventory_item_id: inventoryItem.id,
-}))
-
-await createInventoryLevelsWorkflow(container).run({
- input: {
- inventory_levels: inventoryLevels,
- },
-})
-
-logger.info("Finished seeding inventory levels data.")
-```
-
-You use Query to retrieve the stock location, to use the first location in the application, and the inventory items.
-
-Then, you generate inventory levels for each inventory item, associating it with the first stock location.
-
-Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels.
-
-### Test Script
-
-To test out the script, run the following command in your project's directory:
-
-```bash
-npx medusa exec ./src/scripts/demo-products.ts
-```
-
-This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products.
-
-
-# Data Model’s Primary Key
-
-In this chapter, you’ll learn how to configure the primary key of a data model.
-
-## primaryKey Method
-
-To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
-
-For example:
-
-```ts highlights={highlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- id: model.id().primaryKey(),
- // ...
-})
-
-export default MyCustom
-```
-
-In the example above, the `id` property is defined as the data model's primary key.
-
-
# Data Model Relationships
In this chapter, you’ll learn how to define relationships between data models in your module.
@@ -5262,173 +6429,6 @@ The `cascades` method accepts an object. Its key is the operation’s name, such
In the example above, when a store is deleted, its associated products are also deleted.
-# Searchable Data Model Property
-
-In this chapter, you'll learn what a searchable property is and how to define it.
-
-## What is a Searchable Property?
-
-Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
-
-When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
-
-***
-
-## Define a Searchable Property
-
-Use the `searchable` method on a `text` property to indicate that it's searchable.
-
-For example:
-
-```ts highlights={searchableHighlights}
-import { model } from "@medusajs/framework/utils"
-
-const MyCustom = model.define("my_custom", {
- name: model.text().searchable(),
- // ...
-})
-
-export default MyCustom
-```
-
-In this example, the `name` property is searchable.
-
-### Search Example
-
-If you pass a `q` filter to the `listMyCustoms` method:
-
-```ts
-const myCustoms = await helloModuleService.listMyCustoms({
- q: "John",
-})
-```
-
-This retrieves records that include `John` in their `name` property.
-
-
-# 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\"));")
- }
-
- async down(): Promise {
- this.addSql("drop table if exists \"my_custom\" cascade;")
- }
-
-}
-```
-
-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.
-
-***
-
-## Run the Migration
-
-To run your migration, run the following command:
-
-This command also syncs module links. If you don't want that, use the `--skip-links` option.
-
-```bash
-npx medusa db:migrate
-```
-
-This reflects the changes in the database as implemented in the migration's `up` method.
-
-***
-
-## Rollback the Migration
-
-To rollback or revert the last migration you ran for a module, run the following command:
-
-```bash
-npx medusa db:rollback 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).
-
-
-# Event Data Payload
-
-In this chapter, you'll learn how subscribers receive an event's data payload.
-
-## Access Event's Data Payload
-
-When events are emitted, they’re emitted with a data payload.
-
-The object that the subscriber function receives as a parameter has an `event` property, which is an object holding the event payload in a `data` property with additional context.
-
-For example:
-
-```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
-import type {
- SubscriberArgs,
- SubscriberConfig,
-} from "@medusajs/framework"
-
-export default async function productCreateHandler({
- event,
-}: SubscriberArgs<{ id: string }>) {
- const productId = event.data.id
- console.log(`The product ${productId} was created`)
-}
-
-export const config: SubscriberConfig = {
- event: "product.created",
-}
-```
-
-The `event` object has the following properties:
-
-- data: (\`object\`) The data payload of the event. Its properties are different for each event.
-- name: (string) The name of the triggered event.
-- metadata: (\`object\`) Additional data and context of the emitted event.
-
-This logs the product ID received in the `product.created` event’s data payload to the console.
-
-{/* ---
-
-## List of Events with Data Payload
-
-Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */}
-
-
# Data Model Property Types
In this chapter, you’ll learn about the types of properties in a data model’s schema.
@@ -5636,6 +6636,143 @@ export default 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.
+# 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\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"my_custom\" cascade;")
+ }
+
+}
+```
+
+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.
+
+***
+
+## Run the Migration
+
+To run your migration, run the following command:
+
+This command also syncs module links. If you don't want that, use the `--skip-links` option.
+
+```bash
+npx medusa db:migrate
+```
+
+This reflects the changes in the database as implemented in the migration's `up` method.
+
+***
+
+## Rollback the Migration
+
+To rollback or revert the last migration you ran for a module, run the following command:
+
+```bash
+npx medusa db:rollback 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).
+
+
+# Module Link Direction
+
+In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
+
+## Link Direction
+
+The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
+
+For example, the following defines a link from the `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:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.myCustom
+)
+```
+
+The above links are two different links that serve different purposes.
+
+***
+
+## Which Link Direction to Use?
+
+### Extend Data Models
+
+If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
+
+For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
+
+```ts
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.subtitle
+)
+```
+
+### Associate Data Models
+
+If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
+
+For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
+
+```ts
+export default defineLink(
+ HelloModule.linkable.post,
+ ProductModule.linkable.product
+)
+```
+
+
# Add Columns to a Link
In this chapter, you'll learn how to add custom columns to a link definition and manage them.
@@ -5784,6 +6921,276 @@ await link.create({
```
+# Searchable Data Model Property
+
+In this chapter, you'll learn what a searchable property is and how to define it.
+
+## What is a Searchable Property?
+
+Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
+
+When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
+
+***
+
+## Define a Searchable Property
+
+Use the `searchable` method on a `text` property to indicate that it's searchable.
+
+For example:
+
+```ts highlights={searchableHighlights}
+import { model } from "@medusajs/framework/utils"
+
+const MyCustom = model.define("my_custom", {
+ name: model.text().searchable(),
+ // ...
+})
+
+export default MyCustom
+```
+
+In this example, the `name` property is searchable.
+
+### Search Example
+
+If you pass a `q` filter to the `listMyCustoms` method:
+
+```ts
+const myCustoms = await helloModuleService.listMyCustoms({
+ q: "John",
+})
+```
+
+This retrieves records that include `John` in their `name` property.
+
+
+# Query Context
+
+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).
+
+## What is Query Context?
+
+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.
+
+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).
+
+***
+
+## How to Use Query Context
+
+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`).
+
+You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
+
+For example, to retrieve posts using Query while passing the user's language as a context:
+
+```ts
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ }),
+})
+```
+
+In this example, you pass in the context a `lang` property whose value is `es`.
+
+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.
+
+For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
+
+```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"
+
+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
+
+ let posts = await super.listPosts(filters, config, sharedContext)
+
+ if (context.lang === "es") {
+ posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
+
+ return posts
+ }
+}
+
+export default BlogModuleService
+```
+
+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.
+
+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.
+
+All posts returned will now have their titles appended with "en español".
+
+Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
+
+### Using Pagination with Query
+
+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:
+
+```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"
+
+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
+
+ const result = await super.listAndCountPosts(
+ filters,
+ config,
+ sharedContext
+ )
+
+ if (context.lang === "es") {
+ result.posts = posts.map((post) => {
+ return {
+ ...post,
+ title: post.title + " en español",
+ }
+ })
+ }
+
+ return result
+ }
+}
+
+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`.
+
+***
+
+## Passing Query Context to Related Data Models
+
+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 linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
+
+For example, to pass a context for the post's authors:
+
+```ts highlights={highlights3}
+const { data } = await query.graph({
+ entity: "post",
+ fields: ["*"],
+ context: QueryContext({
+ lang: "es",
+ author: QueryContext({
+ lang: "es",
+ }),
+ }),
+})
+```
+
+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"
+
+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
+
+ let posts = await super.listPosts(filters, config, sharedContext)
+
+ const isPostLangEs = context.lang === "es"
+ const isAuthorLangEs = context.author?.lang === "es"
+
+ 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,
+ },
+ }
+ })
+ }
+
+ return posts
+ }
+}
+
+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.
+
+***
+
+## Passing Query Context to Linked Data Models
+
+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.
+
+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:
+
+```ts highlights={highlights5}
+const { data } = await query.graph({
+ entity: "product",
+ fields: ["*", "post.*"],
+ context: {
+ post: QueryContext({
+ lang: "es",
+ }),
+ },
+})
+```
+
+In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
+
+To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
+
+
# Query
In this chapter, you’ll learn about Query and how to use it to fetch data from modules.
@@ -6278,322 +7685,6 @@ await link.restore({
```
-# Query Context
-
-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).
-
-## What is Query Context?
-
-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.
-
-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).
-
-***
-
-## How to Use Query Context
-
-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`).
-
-You initialize a context using `QueryContext` from the Modules SDK. It accepts an object of contexts as an argument.
-
-For example, to retrieve posts using Query while passing the user's language as a context:
-
-```ts
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
- }),
-})
-```
-
-In this example, you pass in the context a `lang` property whose value is `es`.
-
-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.
-
-For example, continuing the example above, you can override the `listPosts` method of the Blog Module's service to handle the context:
-
-```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"
-
-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
-
- let posts = await super.listPosts(filters, config, sharedContext)
-
- if (context.lang === "es") {
- posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
-
- return posts
- }
-}
-
-export default BlogModuleService
-```
-
-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.
-
-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.
-
-All posts returned will now have their titles appended with "en español".
-
-Learn more about the generated `list` method in [this reference](https://docs.medusajs.com/resources/service-factory-reference/methods/list/index.html.md).
-
-### Using Pagination with Query
-
-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:
-
-```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"
-
-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
-
- const result = await super.listAndCountPosts(
- filters,
- config,
- sharedContext
- )
-
- if (context.lang === "es") {
- result.posts = posts.map((post) => {
- return {
- ...post,
- title: post.title + " en español",
- }
- })
- }
-
- return result
- }
-}
-
-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`.
-
-***
-
-## Passing Query Context to Related Data Models
-
-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 linked data models, check out the [next section](#passing-query-context-to-linked-data-models).
-
-For example, to pass a context for the post's authors:
-
-```ts highlights={highlights3}
-const { data } = await query.graph({
- entity: "post",
- fields: ["*"],
- context: QueryContext({
- lang: "es",
- author: QueryContext({
- lang: "es",
- }),
- }),
-})
-```
-
-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"
-
-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
-
- let posts = await super.listPosts(filters, config, sharedContext)
-
- const isPostLangEs = context.lang === "es"
- const isAuthorLangEs = context.author?.lang === "es"
-
- 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,
- },
- }
- })
- }
-
- return posts
- }
-}
-
-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.
-
-***
-
-## Passing Query Context to Linked Data Models
-
-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.
-
-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:
-
-```ts highlights={highlights5}
-const { data } = await query.graph({
- entity: "product",
- fields: ["*", "post.*"],
- context: {
- post: QueryContext({
- lang: "es",
- }),
- },
-})
-```
-
-In this example, you retrieve products and their associated posts. You also pass a context for `post`, indicating the customer's language.
-
-To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context).
-
-
-# Architectural Modules
-
-In this chapter, you’ll learn about architectural modules.
-
-## What is an Architectural Module?
-
-An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
-
-Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
-
-***
-
-## Architectural Module Types
-
-There are different architectural module types including:
-
-
-
-- Cache Module: Defines the caching mechanism or logic to cache computational results.
-- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
-- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
-- File Module: Integrates a storage service to handle uploading and managing files.
-- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
-
-***
-
-## Architectural Modules List
-
-Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
-
-
-# Module Link Direction
-
-In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case.
-
-## Link Direction
-
-The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`.
-
-For example, the following defines a link from the `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:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- HelloModule.linkable.myCustom
-)
-```
-
-The above links are two different links that serve different purposes.
-
-***
-
-## Which Link Direction to Use?
-
-### Extend Data Models
-
-If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model.
-
-For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it:
-
-```ts
-export default defineLink(
- ProductModule.linkable.product,
- HelloModule.linkable.subtitle
-)
-```
-
-### Associate Data Models
-
-If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model.
-
-For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`:
-
-```ts
-export default defineLink(
- HelloModule.linkable.post,
- ProductModule.linkable.product
-)
-```
-
-
# Commerce Modules
In this chapter, you'll learn about Medusa's commerce modules.
@@ -6638,346 +7729,71 @@ export const countProductsStep = createStep(
Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features.
-# Create a Plugin
+# Module Container
-In this chapter, you'll learn how to create a Medusa plugin and publish it.
+In this chapter, you'll learn about the module's container and how to resolve resources in that container.
-A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community.
+Since modules are isolated, each module has a local container only used by the resources of that module.
-Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
-## 1. Create a Plugin Project
+### List of Registered Resources
-Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it.
-
-Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project:
-
-```bash
-npx create-medusa-app my-plugin --plugin
-```
-
-This will create a new Medusa plugin project in the `my-plugin` directory.
-
-### Plugin Directory Structure
-
-After the installation is done, the plugin structure will look like this:
-
-
-
-- `src/`: Contains the Medusa customizations.
-- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
-- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes.
-- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
-- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-- `src/provider`: Contains [module providers](#create-module-providers).
-- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
-- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`.
-- `package.json`: Contains the plugin's package information, including general information and dependencies.
-- `tsconfig.json`: Contains the TypeScript configuration for the plugin.
+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).
***
-## 2. Prepare Plugin
+## Resolve Resources
-Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application.
+### Services
+
+A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
For example:
-```json title="package.json"
-{
- "name": "@myorg/plugin-name",
- // ...
+```ts highlights={[["4"], ["10"]]}
+import { Logger } from "@medusajs/framework/types"
+
+type InjectedDependencies = {
+ logger: Logger
}
-```
-In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
+export default class HelloModuleService {
+ protected logger_: Logger
-```json title="package.json"
-{
- "keywords": [
- "medusa-plugin",
- "medusa-v2"
- ],
- // ...
-}
-```
+ constructor({ logger }: InjectedDependencies) {
+ this.logger_ = logger
-***
-
-## 3. Publish Plugin Locally for Development and Testing
-
-Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it.
-
-### Publish and Install Local Package
-
-### Prerequisites
-
-- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md)
-
-The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process.
-
-To publish the plugin to the local registry, run the following command in your plugin project:
-
-```bash title="Plugin project"
-npx medusa plugin:publish
-```
-
-This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
-
-Next, navigate to your Medusa application:
-
-```bash title="Medusa application"
-cd ~/path/to/medusa-app
-```
-
-Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application.
-
-Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency:
-
-```bash npm2yarn title="Medusa application"
-npm install --save-dev yalc
-```
-
-After that, run the following Medusa CLI command to install the plugin:
-
-```bash title="Medusa application"
-npx medusa plugin:add @myorg/plugin-name
-```
-
-Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application.
-
-### Register Plugin in Medusa Application
-
-After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`.
-
-Add the plugin to the `plugins` array in the `medusa-config.ts` file:
-
-```ts title="medusa-config.ts" highlights={pluginHighlights}
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "@myorg/plugin-name",
- options: {},
- },
- ],
-})
-```
-
-The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package.
-
-#### Pass Module Options through Plugin
-
-Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules.
-
-For example:
-
-```ts title="medusa-config.ts" highlights={pluginOptionsHighlight}
-module.exports = defineConfig({
- // ...
- plugins: [
- {
- resolve: "@myorg/plugin-name",
- options: {
- apiKey: true,
- },
- },
- ],
-})
-```
-
-The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md).
-
-### Watch Plugin Changes During Development
-
-While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development.
-
-To do that, run the following command in your plugin project:
-
-```bash title="Plugin project"
-npx medusa plugin:develop
-```
-
-This command will:
-
-- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built.
-- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions.
-
-### Start Medusa Application
-
-You can start your Medusa application's development server to test out your plugin:
-
-```bash npm2yarn title="Medusa application"
-npm run dev
-```
-
-While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application.
-
-***
-
-## 4. Create Customizations in the Plugin
-
-You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin.
-
-- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md)
-- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md)
-- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md)
-- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md)
-- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md)
-- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md)
-- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md)
-- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md)
-- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md)
-
-While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application).
-
-### Generating Migrations for Modules
-
-During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command:
-
-```bash title="Plugin project"
-npx medusa plugin:db:generate
-```
-
-This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command:
-
-```bash title="Medusa application"
-npx medusa db:migrate
-```
-
-### Importing Module Resources
-
-Your plugin project should have the following exports in `package.json`:
-
-```json title="package.json"
-{
- "exports": {
- "./package.json": "./package.json",
- "./workflows": "./.medusa/server/src/workflows/index.js",
- "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
- "./providers/*": "./.medusa/server/src/providers/*/index.js",
- "./*": "./.medusa/server/src/*.js"
+ this.logger_.info("[HelloModuleService]: Hello World!")
}
+
+ // ...
}
```
-Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export.
+### Loader
-The plugin exports the following files and directories:
+A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
-- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin.
-- `./workflows`: The workflows exported in `./src/workflows/index.ts`.
-- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application.
-- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application.
-- `./*`: Any other files in the plugin's `src` directory.
+For example:
-With these exports, you can import the plugin's resources in the Medusa application's code like this:
+```ts highlights={[["9"]]}
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
-`@myorg/plugin-name` is the plugin package's name.
+export default async function helloWorldLoader({
+ container,
+}: LoaderOptions) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-```ts
-import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows"
-import BlogModule from "@myorg/plugin-name/modules/blog"
-// import other files created in plugin like ./src/types/blog.ts
-import BlogType from "@myorg/plugin-name/types/blog"
+ logger.info("[helloWorldLoader]: Hello, World!")
+}
```
-And you can register a module provider in the Medusa application's `medusa-config.ts` like this:
-
-```ts highlights={[["9"]]} title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/notification",
- options: {
- providers: [
- {
- resolve: "@myorg/plugin-name/providers/my-notification",
- id: "my-notification",
- options: {
- channels: ["email"],
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin.
-
-### Create Module Providers
-
-To learn how to create module providers, refer to the following guides:
-
-- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md)
-- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md)
-- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md)
-- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md)
-- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md)
-- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md)
-
-***
-
-## 5. Publish Plugin to NPM
-
-Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
-
-```bash
-npx medusa plugin:build
-```
-
-The command will compile an output in the `.medusa/server` directory.
-
-You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm:
-
-```bash
-npm publish
-```
-
-If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application.
-
-### Install Public Plugin in Medusa Application
-
-You install a plugin that's published publicly using your package manager. For example:
-
-```bash npm2yarn
-npm install @myorg/plugin-name
-```
-
-Where `@myorg/plugin-name` is the name of your plugin as published on NPM.
-
-Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application).
-
-***
-
-## Update a Published Plugin
-
-To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md).
-
-If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again.
-
-First, run the following command to change the version of the plugin:
-
-```bash
-npm version
-```
-
-Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information.
-
-Then, re-run the same commands for publishing a plugin:
-
-```bash
-npx medusa plugin:build
-npm publish
-```
-
-This will publish an updated version of your plugin under a new version.
-
# Perform Database Operations in a Service
@@ -7400,6 +8216,107 @@ class HelloModuleService {
```
+# Module Isolation
+
+In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
+
+- Modules can't access resources, such as services or data models, from other modules.
+- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
+
+## How are Modules Isolated?
+
+A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
+
+For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
+
+***
+
+## Why are Modules Isolated
+
+Some of the module isolation's benefits include:
+
+- Integrate your module into any Medusa application without side-effects to your setup.
+- Replace existing modules with your custom implementation, if your use case is drastically different.
+- Use modules in other environments, such as Edge functions and Next.js apps.
+
+***
+
+## How to Extend Data Model of Another Module?
+
+To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+***
+
+## How to Use Services of Other Modules?
+
+If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
+
+Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
+
+### Example
+
+For example, consider you have two modules:
+
+1. A module that stores and manages brands in your application.
+2. A module that integrates a third-party Content Management System (CMS).
+
+To sync brands from your application to the third-party system, create the following steps:
+
+```ts title="Example Steps" highlights={stepsHighlights}
+const retrieveBrandsStep = createStep(
+ "retrieve-brands",
+ async (_, { container }) => {
+ const brandModuleService = container.resolve(
+ "brandModuleService"
+ )
+
+ const brands = await brandModuleService.listBrands()
+
+ return new StepResponse(brands)
+ }
+)
+
+const createBrandsInCmsStep = createStep(
+ "create-brands-in-cms",
+ async ({ brands }, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ const cmsBrands = await cmsModuleService.createBrands(brands)
+
+ return new StepResponse(cmsBrands, cmsBrands)
+ },
+ async (brands, { container }) => {
+ const cmsModuleService = container.resolve(
+ "cmsModuleService"
+ )
+
+ await cmsModuleService.deleteBrands(
+ brands.map((brand) => brand.id)
+ )
+ }
+)
+```
+
+The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
+
+Then, create the following workflow that uses these steps:
+
+```ts title="Example Workflow"
+export const syncBrandsWorkflow = createWorkflow(
+ "sync-brands",
+ () => {
+ const brands = retrieveBrandsStep()
+
+ createBrandsInCmsStep({ brands })
+ }
+)
+```
+
+You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
+
+
# Loaders
In this chapter, you’ll learn about loaders and how to use them.
@@ -7643,134 +8560,6 @@ info: Connected to MongoDB
You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.
-# Modules Directory Structure
-
-In this document, you'll learn about the expected files and directories in your module.
-
-
-
-## index.ts
-
-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).
-
-***
-
-## service.ts
-
-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).
-
-***
-
-## Other Directories
-
-The following directories are optional and their content are explained more in the following chapters:
-
-- `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.
-
-
-# Module Isolation
-
-In this chapter, you'll learn how modules are isolated, and what that means for your custom development.
-
-- Modules can't access resources, such as services or data models, from other modules.
-- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules.
-
-## How are Modules Isolated?
-
-A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module.
-
-For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models.
-
-***
-
-## Why are Modules Isolated
-
-Some of the module isolation's benefits include:
-
-- Integrate your module into any Medusa application without side-effects to your setup.
-- Replace existing modules with your custom implementation, if your use case is drastically different.
-- Use modules in other environments, such as Edge functions and Next.js apps.
-
-***
-
-## How to Extend Data Model of Another Module?
-
-To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-***
-
-## How to Use Services of Other Modules?
-
-If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities.
-
-Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output.
-
-### Example
-
-For example, consider you have two modules:
-
-1. A module that stores and manages brands in your application.
-2. A module that integrates a third-party Content Management System (CMS).
-
-To sync brands from your application to the third-party system, create the following steps:
-
-```ts title="Example Steps" highlights={stepsHighlights}
-const retrieveBrandsStep = createStep(
- "retrieve-brands",
- async (_, { container }) => {
- const brandModuleService = container.resolve(
- "brandModuleService"
- )
-
- const brands = await brandModuleService.listBrands()
-
- return new StepResponse(brands)
- }
-)
-
-const createBrandsInCmsStep = createStep(
- "create-brands-in-cms",
- async ({ brands }, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- const cmsBrands = await cmsModuleService.createBrands(brands)
-
- return new StepResponse(cmsBrands, cmsBrands)
- },
- async (brands, { container }) => {
- const cmsModuleService = container.resolve(
- "cmsModuleService"
- )
-
- await cmsModuleService.deleteBrands(
- brands.map((brand) => brand.id)
- )
- }
-)
-```
-
-The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module.
-
-Then, create the following workflow that uses these steps:
-
-```ts title="Example Workflow"
-export const syncBrandsWorkflow = createWorkflow(
- "sync-brands",
- () => {
- const brands = retrieveBrandsStep()
-
- createBrandsInCmsStep({ brands })
- }
-)
-```
-
-You can then use this workflow in an API route, scheduled job, or other resources that use this functionality.
-
-
# Multiple Services in a Module
In this chapter, you'll learn how to use multiple services in a module.
@@ -7899,6 +8688,227 @@ The `configModule` has a `modules` property that includes all registered modules
If its value is not a `boolean`, set the service's options to the module configuration's `options` property.
+# Modules Directory Structure
+
+In this document, you'll learn about the expected files and directories in your module.
+
+
+
+## index.ts
+
+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).
+
+***
+
+## service.ts
+
+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).
+
+***
+
+## Other Directories
+
+The following directories are optional and their content are explained more in the following chapters:
+
+- `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.
+
+
+# Module Options
+
+In this chapter, you’ll learn about passing options to your module from the Medusa application’s configurations and using them in the module’s resources.
+
+## What are Module Options?
+
+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.
+
+***
+
+## How to Pass Options to a Module?
+
+To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`.
+
+For example:
+
+```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.
+
+### Pass Options to a Module in a Plugin
+
+If your module is part of a plugin, you can pass options to the module in the plugin’s configuration.
+
+For example:
+
+```ts title="medusa-config.ts"
+import { defineConfig } from "@medusajs/framework/utils"
+module.exports = defineConfig({
+ plugins: [
+ {
+ resolve: "@myorg/plugin-name",
+ options: {
+ capitalize: true,
+ },
+ },
+ ],
+})
+```
+
+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.
+
+For example:
+
+```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"
+
+// recommended to define type in another file
+type ModuleOptions = {
+ capitalize?: boolean
+}
+
+export default class HelloModuleService extends MedusaService({
+ MyCustom,
+}){
+ protected options_: ModuleOptions
+
+ constructor({}, options?: ModuleOptions) {
+ super(...arguments)
+
+ this.options_ = options || {
+ capitalize: false,
+ }
+ }
+
+ // ...
+}
+```
+
+***
+
+## Access Module Options in Loader
+
+The object that a module’s loaders receive as a parameter has an `options` property holding the module's options.
+
+For example:
+
+```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"
+
+// recommended to define type in another file
+type ModuleOptions = {
+ capitalize?: boolean
+}
+
+export default async function helloWorldLoader({
+ options,
+}: LoaderOptions) {
+
+ console.log(
+ "[HELLO MODULE] Just started the Medusa application!",
+ options
+ )
+}
+```
+
+***
+
+## Validate Module Options
+
+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, 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, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`:
+
+```ts title="src/modules/hello/loaders/validate.ts"
+import { LoaderOptions } from "@medusajs/framework/types"
+import { MedusaError } from "@medusajs/framework/utils"
+
+// 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."
+ )
+ }
+}
+```
+
+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],
+})
+```
+
+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.
+
+
+# Architectural Modules
+
+In this chapter, you’ll learn about architectural modules.
+
+## What is an Architectural Module?
+
+An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure.
+
+Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
+
+***
+
+## Architectural Module Types
+
+There are different architectural module types including:
+
+
+
+- Cache Module: Defines the caching mechanism or logic to cache computational results.
+- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
+- Workflow Engine Module: Integrates a service to store and track workflow executions and steps.
+- File Module: Integrates a storage service to handle uploading and managing files.
+- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
+
+***
+
+## Architectural Modules List
+
+Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
+
+
# Service Constraints
This chapter lists constraints to keep in mind when creating a service.
@@ -8112,167 +9122,381 @@ export default HelloModuleService
```
-# Module Options
+# Create a Plugin
-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 create a Medusa plugin and publish it.
-## What are Module Options?
+A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community.
-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.
+Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0).
+
+## 1. Create a Plugin Project
+
+Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it.
+
+Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project:
+
+```bash
+npx create-medusa-app my-plugin --plugin
+```
+
+This will create a new Medusa plugin project in the `my-plugin` directory.
+
+### Plugin Directory Structure
+
+After the installation is done, the plugin structure will look like this:
+
+
+
+- `src/`: Contains the Medusa customizations.
+- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md).
+- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes.
+- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md).
+- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
+- `src/provider`: Contains [module providers](#create-module-providers).
+- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md).
+- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`.
+- `package.json`: Contains the plugin's package information, including general information and dependencies.
+- `tsconfig.json`: Contains the TypeScript configuration for the plugin.
***
-## How to Pass Options to a Module?
+## 2. Prepare Plugin
-To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`.
+### Package Name
+
+Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application.
For example:
-```ts title="medusa-config.ts"
+```json title="package.json"
+{
+ "name": "@myorg/plugin-name",
+ // ...
+}
+```
+
+### Package Keywords
+
+In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website:
+
+```json title="package.json"
+{
+ "keywords": [
+ "medusa-plugin",
+ "medusa-v2"
+ ],
+ // ...
+}
+```
+
+### Package Dependencies
+
+Your plugin project will already have the dependencies mentioned in this section. If you haven't made any changes to the dependencies, you can skip this section.
+
+In the `package.json` file you must have the Medusa dependencies as `devDependencies` and `peerDependencies`. In addition, you must have `@swc/core` as a `devDependency`, as it's used by the plugin CLI tools.
+
+For example, assuming `2.5.0` is the latest Medusa version:
+
+```json title="package.json"
+{
+ "devDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/ui": "4.0.4",
+ "@medusajs/icons": "2.5.0",
+ "@swc/core": "1.5.7",
+ },
+ "peerDependencies": {
+ "@medusajs/admin-sdk": "2.5.0",
+ "@medusajs/cli": "2.5.0",
+ "@medusajs/framework": "2.5.0",
+ "@medusajs/test-utils": "2.5.0",
+ "@medusajs/medusa": "2.5.0",
+ "@medusajs/ui": "4.0.3",
+ "@medusajs/icons": "2.5.0",
+ }
+}
+```
+
+***
+
+## 3. Publish Plugin Locally for Development and Testing
+
+Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it.
+
+### Publish and Install Local Package
+
+### Prerequisites
+
+- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md)
+
+The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process.
+
+To publish the plugin to the local registry, run the following command in your plugin project:
+
+```bash title="Plugin project"
+npx medusa plugin:publish
+```
+
+This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`.
+
+Next, navigate to your Medusa application:
+
+```bash title="Medusa application"
+cd ~/path/to/medusa-app
+```
+
+Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application.
+
+Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency:
+
+```bash npm2yarn title="Medusa application"
+npm install --save-dev yalc
+```
+
+After that, run the following Medusa CLI command to install the plugin:
+
+```bash title="Medusa application"
+npx medusa plugin:add @myorg/plugin-name
+```
+
+Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application.
+
+### Register Plugin in Medusa Application
+
+After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`.
+
+Add the plugin to the `plugins` array in the `medusa-config.ts` file:
+
+```ts title="medusa-config.ts" highlights={pluginHighlights}
module.exports = defineConfig({
// ...
- modules: [
+ plugins: [
{
- resolve: "./src/modules/hello",
- options: {
- capitalize: true,
- },
+ resolve: "@myorg/plugin-name",
+ options: {},
},
],
})
```
-The `options` property’s value is an object. You can pass any properties you want.
+The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package.
-### Pass Options to a Module in a Plugin
+#### Pass Module Options through Plugin
-If your module is part of a plugin, you can pass options to the module in the plugin’s configuration.
+Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules.
For example:
-```ts title="medusa-config.ts"
-import { defineConfig } from "@medusajs/framework/utils"
+```ts title="medusa-config.ts" highlights={pluginOptionsHighlight}
module.exports = defineConfig({
+ // ...
plugins: [
{
resolve: "@myorg/plugin-name",
options: {
- capitalize: true,
+ apiKey: true,
},
},
],
})
```
-The `options` property in the plugin configuration is passed to all modules in a plugin.
+The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md).
-***
+### Watch Plugin Changes During Development
-## Access Module Options in Main Service
+While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development.
-The module’s main service receives the module options as a second parameter.
+To do that, run the following command in your plugin project:
-For example:
-
-```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"
-
-// recommended to define type in another file
-type ModuleOptions = {
- capitalize?: boolean
-}
-
-export default class HelloModuleService extends MedusaService({
- MyCustom,
-}){
- protected options_: ModuleOptions
-
- constructor({}, options?: ModuleOptions) {
- super(...arguments)
-
- this.options_ = options || {
- capitalize: false,
- }
- }
-
- // ...
-}
+```bash title="Plugin project"
+npx medusa plugin:develop
```
-***
+This command will:
-## Access Module Options in Loader
+- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built.
+- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions.
-The object that a module’s loaders receive as a parameter has an `options` property holding the module's options.
+### Start Medusa Application
-For example:
+You can start your Medusa application's development server to test out your plugin:
-```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"
-
-// recommended to define type in another file
-type ModuleOptions = {
- capitalize?: boolean
-}
-
-export default async function helloWorldLoader({
- options,
-}: LoaderOptions) {
-
- console.log(
- "[HELLO MODULE] Just started the Medusa application!",
- options
- )
-}
+```bash npm2yarn title="Medusa application"
+npm run dev
```
+While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application.
+
***
-## Validate Module Options
+## 4. Create Customizations in the Plugin
-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.
+You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin.
-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.
+- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md)
+- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md)
+- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md)
+- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md)
+- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md)
+- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md)
+- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md)
+- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md)
+- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md)
-For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`:
+While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application).
-```ts title="src/modules/hello/loaders/validate.ts"
-import { LoaderOptions } from "@medusajs/framework/types"
-import { MedusaError } from "@medusajs/framework/utils"
+### Generating Migrations for Modules
-// recommended to define type in another file
-type ModuleOptions = {
- apiKey?: string
-}
+During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command:
-export default async function validationLoader({
- options,
-}: LoaderOptions) {
- if (!options.apiKey) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Hello Module requires an apiKey option."
- )
+```bash title="Plugin project"
+npx medusa plugin:db:generate
+```
+
+This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command:
+
+```bash title="Medusa application"
+npx medusa db:migrate
+```
+
+### Importing Module Resources
+
+Your plugin project should have the following exports in `package.json`:
+
+```json title="package.json"
+{
+ "exports": {
+ "./package.json": "./package.json",
+ "./workflows": "./.medusa/server/src/workflows/index.js",
+ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
+ "./providers/*": "./.medusa/server/src/providers/*/index.js",
+ "./*": "./.medusa/server/src/*.js"
}
}
```
-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):
+Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export.
-```ts title="src/modules/hello/index.ts"
-// other imports...
-import validationLoader from "./loaders/validate"
+The plugin exports the following files and directories:
-export default Module("hello", {
+- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin.
+- `./workflows`: The workflows exported in `./src/workflows/index.ts`.
+- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application.
+- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application.
+- `./*`: Any other files in the plugin's `src` directory.
+
+With these exports, you can import the plugin's resources in the Medusa application's code like this:
+
+`@myorg/plugin-name` is the plugin package's name.
+
+```ts
+import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows"
+import BlogModule from "@myorg/plugin-name/modules/blog"
+// import other files created in plugin like ./src/types/blog.ts
+import BlogType from "@myorg/plugin-name/types/blog"
+```
+
+And you can register a module provider in the Medusa application's `medusa-config.ts` like this:
+
+```ts highlights={[["9"]]} title="medusa-config.ts"
+module.exports = defineConfig({
// ...
- loaders: [validationLoader],
+ modules: [
+ {
+ resolve: "@medusajs/medusa/notification",
+ options: {
+ providers: [
+ {
+ resolve: "@myorg/plugin-name/providers/my-notification",
+ id: "my-notification",
+ options: {
+ channels: ["email"],
+ // provider options...
+ },
+ },
+ ],
+ },
+ },
+ ],
})
```
-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.
+You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin.
+
+### Create Module Providers
+
+To learn how to create module providers, refer to the following guides:
+
+- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md)
+- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md)
+- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md)
+- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md)
+- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md)
+- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md)
+
+***
+
+## 5. Publish Plugin to NPM
+
+Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project:
+
+```bash
+npx medusa plugin:build
+```
+
+The command will compile an output in the `.medusa/server` directory.
+
+You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm:
+
+```bash
+npm publish
+```
+
+If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application.
+
+### Install Public Plugin in Medusa Application
+
+You install a plugin that's published publicly using your package manager. For example:
+
+```bash npm2yarn
+npm install @myorg/plugin-name
+```
+
+Where `@myorg/plugin-name` is the name of your plugin as published on NPM.
+
+Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application).
+
+***
+
+## Update a Published Plugin
+
+To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md).
+
+If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again.
+
+First, run the following command to change the version of the plugin:
+
+```bash
+npm version
+```
+
+Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information.
+
+Then, re-run the same commands for publishing a plugin:
+
+```bash
+npx medusa plugin:build
+npm publish
+```
+
+This will publish an updated version of your plugin under a new version.
# Scheduled Jobs Number of Executions
@@ -8305,759 +9529,6 @@ So, it'll only execute 3 times, each every minute, then it won't be executed any
If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified.
-# Environment Variables in Admin Customizations
-
-In this chapter, you'll learn how to use environment variables in your admin customizations.
-
-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).
-
-## How to Set Environment Variables
-
-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_`.
-
-For example:
-
-```plain
-VITE_MY_API_KEY=sk_123
-```
-
-***
-
-## How to Use Environment Variables
-
-To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object.
-
-For example:
-
-```tsx highlights={[["8"]]}
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const ProductWidget = () => {
- return (
-
-
- API Key: {import.meta.env.VITE_MY_API_KEY}
-
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`.
-
-### Type Error on import.meta.env
-
-If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content:
-
-```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",
-})
-
-export default ProductWidget
-```
-
-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.
-
-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.
-
-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.
-
-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"
-
-export async function loader() {
- // TODO fetch products
-
- return {
- products: [],
- }
-}
-
-const CustomPage = () => {
- const { products } = useLoaderData() as Awaited>
-
- return (
-
-
-
- Products count: {products.length}
-
-
-
- )
-}
-
-export default CustomPage
-```
-
-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.
-
-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.
-
-### Route Parameters
-
-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`:
-
-```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={loaderParamHighlights}
-import { Container, Heading } from "@medusajs/ui"
-import {
- useLoaderData,
- LoaderFunctionArgs,
-} from "react-router-dom"
-
-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.
-
-***
-
-## Other React Router Utilities
-
-### 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 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 Development Constraints
-
-This chapter lists some constraints of admin widgets and UI routes.
-
-## Arrow Functions
-
-Widget and UI route components must be created as arrow functions.
-
-```ts highlights={arrowHighlights}
-// Don't
-function ProductWidget() {
- // ...
-}
-
-// Do
-const ProductWidget = () => {
- // ...
-}
-```
-
-***
-
-## Widget Zone
-
-A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable.
-
-```ts highlights={zoneHighlights}
-// Don't
-export const config = defineWidgetConfig({
- zone: `product.details.before`,
-})
-
-// Don't
-const ZONE = "product.details.after"
-export const config = defineWidgetConfig({
- zone: ZONE,
-})
-
-// Do
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-```
-
-
-# Admin Development Tips
-
-In this chapter, you'll find some tips for your admin development.
-
-## Send Requests to API Routes
-
-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.
-
-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:
-
-```ts
-import Medusa from "@medusajs/js-sdk"
-
-export const sdk = new Medusa({
- baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
- debug: import.meta.env.DEV,
- auth: {
- type: "session",
- },
-})
-```
-
-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).
-
-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:
-
-### Query
-
-```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights}
-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"
-
-const ProductWidget = () => {
- const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list(),
- queryKey: ["products"],
- })
-
- return (
-
- {isLoading && Loading...}
- {data?.products && (
-
- {data.products.map((product) => (
-
{product.title}
- ))}
-
- )}
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.list.before",
-})
-
-export default ProductWidget
-```
-
-### Mutation
-
-```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"
-
-const ProductWidget = ({
- 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 (
-
-
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-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).
-
-### 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).
-
-***
-
-## Global Variables in Admin Customizations
-
-In your admin customizations, you can use the following global variables:
-
-- `__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`.
-
-***
-
-## Admin Translations
-
-The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions.
-
-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).
-
-
-# Admin UI Routes
-
-In this chapter, you’ll learn how to create a UI route in the admin dashboard.
-
-## What is a UI Route?
-
-The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions.
-
-For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa.
-
-***
-
-## How to Create a UI Route?
-
-### Prerequisites
-
-- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md)
-
-You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component.
-
-For example, create the file `src/admin/routes/custom/page.tsx` with the following content:
-
-
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- return (
-
-
- This is my custom route
-
-
- )
-}
-
-export default CustomPage
-```
-
-You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading.
-
-In the route, 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.
-
-The UI route component must be created as an arrow function.
-
-### Test the UI Route
-
-To test the UI route, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page.
-
-***
-
-## Show UI Route in the Sidebar
-
-To add a sidebar item for your custom UI route, export a configuration object in the UI route's file:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- return (
-
-
- This is my custom route
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom Route",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties:
-
-- `label`: the sidebar item’s label.
-- `icon`: an optional React component used as an icon in the sidebar.
-
-The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md).
-
-### Nested UI Routes
-
-Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations:
-
-
-
-```tsx title="src/admin/routes/custom/nested/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const NestedCustomPage = () => {
- return (
-
-
- This is my nested custom route
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Nested Route",
-})
-
-export default NestedCustomPage
-```
-
-This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked.
-
-#### Caveats
-
-Some caveats for nested UI routes in the sidebar:
-
-- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console.
-- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions.
-- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions.
-
-### Route Under Existing Admin Route
-
-You can add a custom UI route under an existing route. For example, you can add a route under the orders route:
-
-```tsx title="src/admin/routes/orders/nested/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const NestedOrdersPage = () => {
- return (
-
-
- Nested Orders Page
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Nested Orders",
- nested: "/orders",
-})
-
-export default NestedOrdersPage
-```
-
-The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item.
-
-***
-
-## Create Settings Page
-
-To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`.
-
-For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`:
-
-
-
-```tsx title="src/admin/routes/settings/custom/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomSettingPage = () => {
- return (
-
-
- Custom Setting Page
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
-})
-
-export default CustomSettingPage
-```
-
-This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`.
-
-***
-
-## Path Parameters
-
-A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`.
-
-For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content:
-
-
-
-```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]}
-import { useParams } from "react-router-dom"
-import { Container, Heading } from "@medusajs/ui"
-
-const CustomPage = () => {
- const { id } = useParams()
-
- return (
-
-
- Passed ID: {id}
-
-
- )
-}
-
-export default CustomPage
-```
-
-You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params).
-
-If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page.
-
-***
-
-## 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.
-
-***
-
-## More Routes Customizations
-
-For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md).
-
-
-# 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 (
-
-
- Product Widget
-
-
- )
-}
-
-// The widget's configurations
-export const config = defineWidgetConfig({
- zone: "product.details.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.
-
-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}
-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.
-
-
# Access Workflow Errors
In this chapter, you’ll learn how to access errors that occur during a workflow’s execution.
@@ -9103,75 +9574,6 @@ The object passed to the `run` method accepts a `throwOnError` property. When di
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
-
-In this chapter, you'll learn how to expose a hook in your workflow.
-
-## When to Expose a Hook
-
-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.
-
-***
-
-## How to Expose a Hook in a Workflow?
-
-To expose a hook in your workflow, use `createHook` from the Workflows SDK.
-
-For example:
-
-```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
-import {
- createStep,
- createHook,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { createProductStep } from "./steps/create-product"
-
-export const myWorkflow = createWorkflow(
- "my-workflow",
- function (input) {
- const product = createProductStep(input)
- const productCreatedHook = createHook(
- "productCreated",
- { productId: product.id }
- )
-
- return new WorkflowResponse(product, {
- hooks: [productCreatedHook],
- })
- }
-)
-```
-
-The `createHook` function accepts two parameters:
-
-1. The first is a string indicating the hook's name. You use this to consume the hook later.
-2. The second is the input to pass to the hook handler.
-
-The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks.
-
-### How to Consume the Hook?
-
-To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
-
-```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
-import { myWorkflow } from "../my-workflow"
-
-myWorkflow.hooks.productCreated(
- async ({ productId }, { container }) => {
- // TODO perform an action
- }
-)
-```
-
-The hook is available on the workflow's `hooks` property using its name `productCreated`.
-
-You invoke the hook, passing a step function (the hook handler) as a parameter.
-
-
# Compensation Function
In this chapter, you'll learn what a compensation function is and how to add it to a step.
@@ -9426,163 +9828,73 @@ The `StepResponse.permanentFailure` fails the step and its workflow, triggering
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.
-# Conditions in Workflows with When-Then
+# Expose a Workflow Hook
-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.
+In this chapter, you'll learn how to expose a hook in your workflow.
-## Why If-Conditions Aren't Allowed in Workflows?
+## When to Expose a Hook
-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.
+Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow.
-So, you can't use an if-condition that checks a variable's value, as the condition will be evaluated when Medusa creates the internal representation of the workflow, rather than during execution.
-
-Instead, use when-then from the Workflows SDK. It allows you to perform steps in a workflow only if a condition that you specify is satisfied.
-
-Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
+Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead.
***
-## How to use When-Then?
+## How to Expose a Hook in a Workflow?
-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.
+To expose a hook in your workflow, use `createHook` from the Workflows SDK.
For example:
-```ts highlights={highlights}
-import {
+```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights}
+import {
+ createStep,
+ createHook,
createWorkflow,
WorkflowResponse,
- when,
} from "@medusajs/framework/workflows-sdk"
-// step imports...
+import { createProductStep } from "./steps/create-product"
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
-
- const result = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- const stepResult = isActiveStep()
- return stepResult
- })
-
- // executed without condition
- const anotherStepResult = anotherStep(result)
-
- return new WorkflowResponse(
- anotherStepResult
+export const myWorkflow = createWorkflow(
+ "my-workflow",
+ function (input) {
+ const product = createProductStep(input)
+ const productCreatedHook = createHook(
+ "productCreated",
+ { productId: product.id }
)
+
+ return new WorkflowResponse(product, {
+ hooks: [productCreatedHook],
+ })
}
)
```
-In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
+The `createHook` function accepts two parameters:
-### When 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.
-`when` accepts the following parameters:
+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.
-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`.
+### How to Consume the Hook?
-### Then Parameters
+To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content:
-To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
+```ts title="src/workflows/hooks/my-workflow.ts" highlights={handlerHighlights}
+import { myWorkflow } from "../my-workflow"
-The callback function is only executed if `when`'s second parameter function returns a `true` value.
-
-***
-
-## Implementing If-Else with When-Then
-
-when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
-
-For example:
-
-```ts highlights={ifElseHighlights}
-const workflow = createWorkflow(
- "workflow",
- function (input: {
- is_active: boolean
- }) {
-
- const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
- }
- ).then(() => {
- return isActiveStep()
- })
-
- const notIsActiveResult = when(
- input,
- (input) => {
- return !input.is_active
- }
- ).then(() => {
- return notIsActiveStep()
- })
-
- // ...
+myWorkflow.hooks.productCreated(
+ async ({ productId }, { 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 hook is available on the workflow's `hooks` property using its name `productCreated`.
-***
-
-## Specify Name for When-Then
-
-Internally, `when-then` blocks have a unique name similar to a step. When you return a step's result in a `when-then` block, the block's name is derived from the step's name. For example:
-
-```ts
-const isActiveResult = when(
- input,
- (input) => {
- return input.is_active
- }
-).then(() => {
- return isActiveStep()
-})
-```
-
-This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
-
-However, if you need to return in your `when-then` block something other than a step's result, you need to specify a unique step name for that block. Otherwise, Medusa will generate a random name for it which can cause unexpected errors in production.
-
-You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
-
-```ts highlights={nameHighlights}
-const { isActive } = when(
- "check-is-active",
- input,
- (input) => {
- return input.is_active
- }
-).then(() => {
- const isActive = isActiveStep()
-
- return {
- isActive,
- }
-})
-```
-
-Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
-
-1. A unique name to be assigned to the `when-then` block.
-2. Either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
-3. A function that returns a boolean indicating whether to execute the action in `then`.
-
-The second and third parameters are the same as the parameters you previously passed to `when`.
+You invoke the hook, passing a step function (the hook handler) as a parameter.
# Workflow Constraints
@@ -10063,6 +10375,165 @@ const workflow = createWorkflow(
In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled.
+# Conditions in Workflows with When-Then
+
+In this chapter, you'll learn how to execute an action based on a condition in a workflow using when-then from the Workflows SDK.
+
+## Why If-Conditions Aren't Allowed in Workflows?
+
+Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. At that point, variables in the workflow don't have any values. They only do when you execute the workflow.
+
+So, you can't use an if-condition that checks a variable's value, as the condition will be evaluated when Medusa creates the internal representation of the workflow, rather than during execution.
+
+Instead, use when-then from the Workflows SDK. It allows you to perform steps in a workflow only if a condition that you specify is satisfied.
+
+Restrictions for conditions is only applicable in a workflow's definition. You can still use if-conditions in your step's code.
+
+***
+
+## How to use When-Then?
+
+The Workflows SDK provides a `when` function that is used to check whether a condition is true. You chain a `then` function to `when` that specifies the steps to execute if the condition in `when` is satisfied.
+
+For example:
+
+```ts highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ when,
+} from "@medusajs/framework/workflows-sdk"
+// step imports...
+
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
+
+ const result = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+ ).then(() => {
+ const stepResult = isActiveStep()
+ return stepResult
+ })
+
+ // executed without condition
+ const anotherStepResult = anotherStep(result)
+
+ return new WorkflowResponse(
+ anotherStepResult
+ )
+ }
+)
+```
+
+In this code snippet, you execute the `isActiveStep` only if the `input.is_active`'s value is `true`.
+
+### When Parameters
+
+`when` accepts the following parameters:
+
+1. The first parameter is either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
+2. The second parameter is a function that returns a boolean indicating whether to execute the action in `then`.
+
+### Then Parameters
+
+To specify the action to perform if the condition is satisfied, chain a `then` function to `when` and pass it a callback function.
+
+The callback function is only executed if `when`'s second parameter function returns a `true` value.
+
+***
+
+## Implementing If-Else with When-Then
+
+when-then doesn't support if-else conditions. Instead, use two `when-then` conditions in your workflow.
+
+For example:
+
+```ts highlights={ifElseHighlights}
+const workflow = createWorkflow(
+ "workflow",
+ function (input: {
+ is_active: boolean
+ }) {
+
+ const isActiveResult = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+ ).then(() => {
+ return isActiveStep()
+ })
+
+ const notIsActiveResult = when(
+ input,
+ (input) => {
+ return !input.is_active
+ }
+ ).then(() => {
+ return notIsActiveStep()
+ })
+
+ // ...
+ }
+)
+```
+
+In the above workflow, you use two `when-then` blocks. The first one performs a step if `input.is_active` is `true`, and the second performs a step if `input.is_active` is `false`, acting as an else condition.
+
+***
+
+## Specify Name for When-Then
+
+Internally, `when-then` blocks have a unique name similar to a step. When you return a step's result in a `when-then` block, the block's name is derived from the step's name. For example:
+
+```ts
+const isActiveResult = when(
+ input,
+ (input) => {
+ return input.is_active
+ }
+).then(() => {
+ return isActiveStep()
+})
+```
+
+This `when-then` block's internal name will be `when-then-is-active`, where `is-active` is the step's name.
+
+However, if you need to return in your `when-then` block something other than a step's result, you need to specify a unique step name for that block. Otherwise, Medusa will generate a random name for it which can cause unexpected errors in production.
+
+You pass a name for `when-then` as a first parameter of `when`, whose signature can accept three parameters in this case. For example:
+
+```ts highlights={nameHighlights}
+const { isActive } = when(
+ "check-is-active",
+ input,
+ (input) => {
+ return input.is_active
+ }
+).then(() => {
+ const isActive = isActiveStep()
+
+ return {
+ isActive,
+ }
+})
+```
+
+Since `then` returns a value different than the step's result, you pass to the `when` function the following parameters:
+
+1. A unique name to be assigned to the `when-then` block.
+2. Either an object or the workflow's input. This data is passed as a parameter to the function in `when`'s second parameter.
+3. A function that returns a boolean indicating whether to execute the action in `then`.
+
+The second and third parameters are the same as the parameters you previously passed to `when`.
+
+
# Long-Running Workflows
In this chapter, you’ll learn what a long-running workflow is and how to configure it.
@@ -10425,146 +10896,6 @@ The `config` method accepts an object with a `name` property. Its value is a new
The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`.
-# Run Workflow Steps in Parallel
-
-In this chapter, you’ll learn how to run workflow steps in parallel.
-
-## parallelize Utility Function
-
-If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK.
-
-The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
-
-For example:
-
-```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import {
- createWorkflow,
- WorkflowResponse,
- parallelize,
-} from "@medusajs/framework/workflows-sdk"
-import {
- createProductStep,
- getProductStep,
- createPricesStep,
- attachProductToSalesChannelStep,
-} from "./steps"
-
-interface WorkflowInput {
- title: string
-}
-
-const myWorkflow = createWorkflow(
- "my-workflow",
- (input: WorkflowInput) => {
- const product = createProductStep(input)
-
- const [prices, productSalesChannel] = parallelize(
- createPricesStep(product),
- attachProductToSalesChannelStep(product)
- )
-
- const id = product.id
- const refetchedProduct = getProductStep(product.id)
-
- return new WorkflowResponse(refetchedProduct)
- }
-)
-```
-
-The `parallelize` function accepts the steps to run in parallel as a parameter.
-
-It returns an array of the steps' results in the same order they're passed to the `parallelize` function.
-
-So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
-
-
-# Retry Failed Steps
-
-In this chapter, you’ll learn how to configure steps to allow retrial on failure.
-
-## Configure a Step’s Retrial
-
-By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
-
-You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- createStep,
- createWorkflow,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- },
- async () => {
- console.log("Executing step 1")
-
- throw new Error("Oops! Something happened.")
- }
-)
-
-const myWorkflow = createWorkflow(
- "hello-world",
- function () {
- const str1 = step1()
-
- return new WorkflowResponse({
- message: str1,
- })
-})
-
-export default myWorkflow
-```
-
-The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
-
-When you execute the above workflow, you’ll see the following result in the terminal:
-
-```bash
-Executing step 1
-Executing step 1
-Executing step 1
-error: Oops! Something happened.
-Error: Oops! Something happened.
-```
-
-The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
-
-***
-
-## Step Retry Intervals
-
-By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
-
-For example:
-
-```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
-const step1 = createStep(
- {
- name: "step-1",
- maxRetries: 2,
- retryInterval: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
-```
-
-### Interval Changes Workflow to Long-Running
-
-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).
-
-
# Store Workflow Executions
In this chapter, you'll learn how to store workflow executions in the database and access them later.
@@ -10710,6 +11041,92 @@ if (workflowExecution.state === "failed") {
Other state values include `done`, `invoking`, and `compensating`.
+# Retry Failed Steps
+
+In this chapter, you’ll learn how to configure steps to allow retrial on failure.
+
+## Configure a Step’s Retrial
+
+By default, when an error occurs in a step, the step and the workflow fail, and the execution stops.
+
+You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ createStep,
+ createWorkflow,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ },
+ async () => {
+ console.log("Executing step 1")
+
+ throw new Error("Oops! Something happened.")
+ }
+)
+
+const myWorkflow = createWorkflow(
+ "hello-world",
+ function () {
+ const str1 = step1()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+export default myWorkflow
+```
+
+The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails.
+
+When you execute the above workflow, you’ll see the following result in the terminal:
+
+```bash
+Executing step 1
+Executing step 1
+Executing step 1
+error: Oops! Something happened.
+Error: Oops! Something happened.
+```
+
+The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail.
+
+***
+
+## Step Retry Intervals
+
+By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step.
+
+For example:
+
+```ts title="src/workflows/hello-world.ts" highlights={[["5"]]}
+const step1 = createStep(
+ {
+ name: "step-1",
+ maxRetries: 2,
+ retryInterval: 2, // 2 seconds
+ },
+ async () => {
+ // ...
+ }
+)
+```
+
+### Interval Changes Workflow to Long-Running
+
+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).
+
+
# Variable Manipulation in Workflows with transform
In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow.
@@ -10915,6 +11332,146 @@ const myWorkflow = createWorkflow(
```
+# Run Workflow Steps in Parallel
+
+In this chapter, you’ll learn how to run workflow steps in parallel.
+
+## parallelize Utility Function
+
+If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK.
+
+The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step.
+
+For example:
+
+```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+import {
+ createWorkflow,
+ WorkflowResponse,
+ parallelize,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ createProductStep,
+ getProductStep,
+ createPricesStep,
+ attachProductToSalesChannelStep,
+} from "./steps"
+
+interface WorkflowInput {
+ title: string
+}
+
+const myWorkflow = createWorkflow(
+ "my-workflow",
+ (input: WorkflowInput) => {
+ const product = createProductStep(input)
+
+ const [prices, productSalesChannel] = parallelize(
+ createPricesStep(product),
+ attachProductToSalesChannelStep(product)
+ )
+
+ const id = product.id
+ const refetchedProduct = getProductStep(product.id)
+
+ return new WorkflowResponse(refetchedProduct)
+ }
+)
+```
+
+The `parallelize` function accepts the steps to run in parallel as a parameter.
+
+It returns an array of the steps' results in the same order they're passed to the `parallelize` function.
+
+So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`.
+
+
+# Workflow Timeout
+
+In this chapter, you’ll learn how to set a timeout for workflows and steps.
+
+## What is a Workflow Timeout?
+
+By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
+
+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.
+
+### Timeout Doesn't Stop Step Execution
+
+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.
+
+***
+
+## Configure Workflow Timeout
+
+The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
+
+In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
+
+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()
+
+ return new WorkflowResponse({
+ message: str1,
+ })
+})
+
+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.
+
+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 () => {
+ // ...
+ }
+)
+```
+
+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`.
+
+
# Workflow Hooks
In this chapter, you'll learn what a workflow hook is and how to consume them.
@@ -11039,172 +11596,160 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
Your hook handler then receives that passed data in the `additional_data` object.
-# Workflow Timeout
+# Guide: Implement Brand Module
-In this chapter, you’ll learn how to set a timeout for workflows and steps.
+In this chapter, you'll build a Brand Module that adds a `brand` table to the database and provides data-management features for it.
-## What is a Workflow Timeout?
+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.
-By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs.
+In a module, you create data models and business logic to manage them. In the next chapters, you'll see how you use the module to build commerce features.
-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.
+Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-### Timeout Doesn't Stop Step Execution
+## 1. Create Module Directory
-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.
+Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/brand` that will hold the Brand Module's files.
+
+
***
-## Configure Workflow Timeout
+## 2. Create Data Model
-The `createWorkflow` function can accept a configuration object instead of the workflow’s name.
+A data model represents a table in the database. You create data models using Medusa's Data Model Language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
-In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds.
+Learn more about data models in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#1-create-data-model/index.html.md).
-For example:
+You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a data model that represents a new `brand` table in the database, create the file `src/modules/brand/models/brand.ts` with the following content:
-```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 () => {
- // ...
- }
-)
+```ts title="src/modules/brand/models/brand.ts"
+import { model } from "@medusajs/framework/utils"
-const myWorkflow = createWorkflow({
- name: "hello-world",
- timeout: 2, // 2 seconds
-}, function () {
- const str1 = step1()
-
- return new WorkflowResponse({
- message: str1,
- })
+export const Brand = model.define("brand", {
+ id: model.id().primaryKey(),
+ name: model.text(),
})
-
-export default myWorkflow
-
```
-This workflow's executions fail if they run longer than two seconds.
+You create a `Brand` data model which has an `id` primary key property, and a `name` text property.
-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`.
+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.
+
+Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md).
***
-## Configure Step Timeout
+## 3. Create Module Service
-Alternatively, you can configure the timeout for a step rather than the entire workflow.
+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.
-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.
+In this step, you'll create the Brand Module's service that provides methods to manage the `Brand` data model. In the next chapters, you'll use this service when exposing custom features that involve managing brands.
-The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds.
+Learn more about services in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#2-create-service/index.html.md).
-For example:
+You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, create the file `src/modules/brand/service.ts` with the following content:
-```tsx
-const step1 = createStep(
- {
- name: "step-1",
- timeout: 2, // 2 seconds
- },
- async () => {
- // ...
- }
-)
+
+
+```ts title="src/modules/brand/service.ts" highlights={serviceHighlights}
+import { MedusaService } from "@medusajs/framework/utils"
+import { Brand } from "./models/brand"
+
+class BrandModuleService extends MedusaService({
+ Brand,
+}) {
+
+}
+
+export default BrandModuleService
```
-This step's executions fail if they run longer than two seconds.
+The `BrandModuleService` extends a class returned by `MedusaService` from the Modules SDK. This function generates a class with data-management methods for your module's data models.
-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`.
+The `MedusaService` function receives an object of the module's data models as a parameter, and generates methods to manage those data models. So, the `BrandModuleService` now has methods like `createBrands` and `retrieveBrand` to manage the `Brand` data model.
+You'll use these methods in the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md).
-# Write Integration Tests
+Find a reference of all generated methods in [this guide](https://docs.medusajs.com/resources/service-factory-reference/index.html.md).
-In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests.
+***
-### Prerequisites
+## 4. Export Module Definition
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+A module must export a definition that tells Medusa the name of the module and its main service. This definition is exported in an `index.ts` file at the module's root directory.
-## medusaIntegrationTestRunner Utility
+So, to export the Brand Module's definition, create the file `src/modules/brand/index.ts` with the following content:
-The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations.
+
-For example:
+```ts title="src/modules/brand/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import BrandModuleService from "./service"
-```ts title="integration-tests/http/test.spec.ts" highlights={highlights}
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+export const BRAND_MODULE = "brand"
-medusaIntegrationTestRunner({
- testSuite: ({ api, getContainer }) => {
- // TODO write tests...
- },
+export default Module(BRAND_MODULE, {
+ service: BrandModuleService,
})
-
-jest.setTimeout(60 * 1000)
```
-The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`.
+You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters:
-`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties:
+1. The module's name (`brand`). You'll use this name when you use this module in other customizations.
+2. An object with a required property `service` indicating the module's main service.
-- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods:
- - `get`: Send a `GET` request to an API route.
- - `post`: Send a `POST` request to an API route.
- - `delete`: Send a `DELETE` request to an API route.
-- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container.
+You export `BRAND_MODULE` to reference the module's name more reliably in other customizations.
-The tests in the `testSuite` function are written using [Jest](https://jestjs.io/).
+***
-### Jest Timeout
+## 5. Add Module to Medusa's Configurations
-Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+To start using your module, you must add it to Medusa's configurations in `medusa-config.ts`.
-```ts title="integration-tests/http/test.spec.ts"
-// in your test's file
-jest.setTimeout(60 * 1000)
+The object passed to `defineConfig` in `medusa-config.ts` accepts a `modules` property, whose value is an array of modules to add to the application. So, add the following in `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/brand",
+ },
+ ],
+})
```
+The Brand Module is now added to your Medusa application. You'll start using it in the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md).
+
***
-### Run Tests
+## 6. Generate and Run Migrations
-Run the following command to run your tests:
+A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations ensure that your module is re-usable and removes friction when working in a team, making it easy to reflect changes across team members' databases.
-```bash npm2yarn
-npm run test:integration
+Learn more about migrations in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#5-generate-migrations/index.html.md).
+
+[Medusa's CLI tool](https://docs.medusajs.com/resources/medusa-cli/index.html.md) allows you to generate migration files for your module, then run those migrations to reflect the changes in the database. So, run the following commands in your Medusa application's directory:
+
+```bash
+npx medusa db:generate brand
+npx medusa db:migrate
```
-If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available under the `src/integrations/http` directory.
+The `db:generate` command accepts as an argument the name of the module to generate the migrations for, and the `db:migrate` command runs all migrations that haven't been run yet in the Medusa application.
***
-## Other Options and Inputs
+## Next Step: Create Brand Workflow
-Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function.
+The Brand Module now creates a `brand` table in the database and provides a class to manage its records.
-***
-
-## Database Used in Tests
-
-The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end.
-
-To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md).
-
-***
-
-## Example Integration Tests
-
-The next chapters provide examples of writing integration tests for API routes and workflows.
+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 API Route
@@ -11415,281 +11960,6 @@ 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.
-# 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)
-```
-
-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 Tests
-
-Run the following command to run your module integration tests:
-
-```bash npm2yarn
-npm run test:integration:modules
-```
-
-If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory.
-
-***
-
-## Pass Module Options
-
-If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter.
-
-For example:
-
-```ts
-import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
-import 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).
-
-
-# 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.
-
-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 a module, you create data models and business logic to manage them. In the next chapters, you'll see how you use the module to build commerce features.
-
-Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-## 1. Create Module Directory
-
-Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/brand` that will hold the Brand Module's files.
-
-
-
-***
-
-## 2. Create Data Model
-
-A data model represents a table in the database. You create data models using Medusa's Data Model Language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.
-
-Learn more about data models in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#1-create-data-model/index.html.md).
-
-You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a data model that represents a new `brand` table in the database, create the file `src/modules/brand/models/brand.ts` with the following content:
-
-
-
-```ts title="src/modules/brand/models/brand.ts"
-import { model } from "@medusajs/framework/utils"
-
-export const Brand = model.define("brand", {
- id: model.id().primaryKey(),
- name: model.text(),
-})
-```
-
-You create a `Brand` data model which has an `id` primary key property, and a `name` text property.
-
-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.
-
-Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md).
-
-***
-
-## 3. Create Module 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.
-
-In this step, you'll create the Brand Module's service that provides methods to manage the `Brand` data model. In the next chapters, you'll use this service when exposing custom features that involve managing brands.
-
-Learn more about services in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#2-create-service/index.html.md).
-
-You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, create the file `src/modules/brand/service.ts` with the following content:
-
-
-
-```ts title="src/modules/brand/service.ts" highlights={serviceHighlights}
-import { MedusaService } from "@medusajs/framework/utils"
-import { Brand } from "./models/brand"
-
-class BrandModuleService extends MedusaService({
- Brand,
-}) {
-
-}
-
-export default BrandModuleService
-```
-
-The `BrandModuleService` extends a class returned by `MedusaService` from the Modules SDK. This function generates a class with data-management methods for your module's data models.
-
-The `MedusaService` function receives an object of the module's data models as a parameter, and generates methods to manage those data models. So, the `BrandModuleService` now has methods like `createBrands` and `retrieveBrand` to manage the `Brand` data model.
-
-You'll use these methods in the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md).
-
-Find a reference of all generated methods in [this guide](https://docs.medusajs.com/resources/service-factory-reference/index.html.md).
-
-***
-
-## 4. Export Module Definition
-
-A module must export a definition that tells Medusa the name of the module and its main service. This definition is exported in an `index.ts` file at the module's root directory.
-
-So, to export the Brand Module's definition, create the file `src/modules/brand/index.ts` with the following content:
-
-
-
-```ts title="src/modules/brand/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import BrandModuleService from "./service"
-
-export const BRAND_MODULE = "brand"
-
-export default Module(BRAND_MODULE, {
- service: BrandModuleService,
-})
-```
-
-You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters:
-
-1. The module's name (`brand`). You'll use this name when you use this module in other customizations.
-2. An object with a required property `service` indicating the module's main service.
-
-You export `BRAND_MODULE` to reference the module's name more reliably in other customizations.
-
-***
-
-## 5. Add Module to Medusa's Configurations
-
-To start using your module, you must add it to Medusa's configurations in `medusa-config.ts`.
-
-The object passed to `defineConfig` in `medusa-config.ts` accepts a `modules` property, whose value is an array of modules to add to the application. So, add the following in `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/brand",
- },
- ],
-})
-```
-
-The Brand Module is now added to your Medusa application. You'll start using it in the [next chapter](https://docs.medusajs.com/learn/customization/custom-features/workflow/index.html.md).
-
-***
-
-## 6. Generate and Run Migrations
-
-A migration is a TypeScript or JavaScript file that defines database changes made by a module. Migrations ensure that your module is re-usable and removes friction when working in a team, making it easy to reflect changes across team members' databases.
-
-Learn more about migrations in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#5-generate-migrations/index.html.md).
-
-[Medusa's CLI tool](https://docs.medusajs.com/resources/medusa-cli/index.html.md) allows you to generate migration files for your module, then run those migrations to reflect the changes in the database. So, run the following commands in your Medusa application's directory:
-
-```bash
-npx medusa db:generate brand
-npx medusa db:migrate
-```
-
-The `db:generate` command accepts as an argument the name of the module to generate the migrations for, and the `db:migrate` command runs all migrations that haven't been run yet in the Medusa application.
-
-***
-
-## Next Step: Create Brand Workflow
-
-The Brand Module now creates a `brand` table in the database and provides a class to manage its records.
-
-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
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.
@@ -12200,6 +12470,76 @@ Your customizations often span across systems, where you need to retrieve data o
In the next chapters, you'll learn about the concepts that facilitate integrating third-party systems in your application. You'll integrate a dummy third-party system and sync the brands between it and the Medusa application.
+# Guide: Define Module Link Between Brand and Product
+
+In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
+
+Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
+
+A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
+
+In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
+
+Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
+
+### Prerequisites
+
+- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+
+## 1. Define Link
+
+Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
+
+So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
+
+
+
+```ts title="src/links/product-brand.ts" highlights={highlights}
+import BrandModule from "../modules/brand"
+import ProductModule from "@medusajs/medusa/product"
+import { defineLink } from "@medusajs/framework/utils"
+
+export default defineLink(
+ {
+ linkable: ProductModule.linkable.product,
+ isList: true,
+ },
+ BrandModule.linkable.brand
+)
+```
+
+You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
+
+The `defineLink` function accepts two parameters of the same type, which is either:
+
+- The data model's link configuration, which you access from the Module's `linkable` property;
+- Or an object that has two properties:
+ - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
+ - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
+
+So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
+
+***
+
+## 2. Sync the Link to the Database
+
+A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
+
+```bash
+npx medusa db:migrate
+```
+
+This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
+
+You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
+
+***
+
+## Next Steps: Extend Create Product Flow
+
+In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
+
+
# Guide: Add Product's Brand Widget in Admin
In this chapter, you'll customize the product details page of the Medusa Admin dashboard to show the product's [brand](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md). You'll create a widget that is injected into a pre-defined zone in the page, and in the widget you'll retrieve the product's brand from the server and display it.
@@ -12354,6 +12694,142 @@ The [Admin Components guides](https://docs.medusajs.com/resources/admin-componen
In the next chapter, you'll add a UI route that displays the list of brands in your application and allows admin users.
+# Guide: Query Product's Brands
+
+In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand.
+
+In this chapter, you'll learn how to retrieve a product's brand (and vice-versa) in two ways: Using Medusa's existing API route, or in customizations, such as a custom API route.
+
+### Prerequisites
+
+- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
+- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md)
+
+***
+
+## Approach 1: Retrieve Brands in Existing API Routes
+
+Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
+
+Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
+
+For example, send the following request to retrieve the list of products with their brands:
+
+```bash
+curl 'http://localhost:9000/admin/products?fields=+brand.*' \
+--header 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+Any product that is linked to a brand will have a `brand` property in its object:
+
+```json title="Example Product Object"
+{
+ "id": "prod_123",
+ // ...
+ "brand": {
+ "id": "01JEB44M61BRM3ARM2RRMK7GJF",
+ "name": "Acme",
+ "created_at": "2024-12-05T09:59:08.737Z",
+ "updated_at": "2024-12-05T09:59:08.737Z",
+ "deleted_at": null
+ }
+}
+```
+
+By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
+
+***
+
+## Approach 2: Use Query to Retrieve Linked Records
+
+You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
+
+Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
+
+For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
+
+```ts title="src/api/admin/brands/route.ts" highlights={highlights}
+// other imports...
+import {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+
+export const GET = async (
+ req: MedusaRequest,
+ res: MedusaResponse
+) => {
+ const query = req.scope.resolve("query")
+
+ const { data: brands } = await query.graph({
+ entity: "brand",
+ fields: ["*", "products.*"],
+ })
+
+ res.json({ brands })
+}
+```
+
+This adds a `GET` API route at `/admin/brands`. In the API route, you resolve Query from the Medusa container. Query has a `graph` method that runs a query to retrieve data. It accepts an object having the following properties:
+
+- `entity`: The data model's name as specified in the first parameter of `model.define`.
+- `fields`: An array of properties and relations to retrieve. You can pass:
+ - A property's name, such as `id`, or `*` for all properties.
+ - A relation or linked model's name, such as `products` (use the plural name since brands are linked to list of products). You suffix the name with `.*` to retrieve all its properties.
+
+`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
+
+### Test it Out
+
+To test the API route out, send a `GET` request to `/admin/brands`:
+
+```bash
+curl 'http://localhost:9000/admin/brands' \
+-H 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
+
+This returns the brands in your store with their linked products. For example:
+
+```json title="Example Response"
+{
+ "brands": [
+ {
+ "id": "123",
+ // ...
+ "products": [
+ {
+ "id": "prod_123",
+ // ...
+ }
+ ]
+ }
+ ]
+}
+```
+
+***
+
+## Summary
+
+By following the examples of the previous chapters, you:
+
+- Defined a link between the Brand and Product modules's data models, allowing you to associate a product with a brand.
+- Extended the create-product workflow and route to allow setting the product's brand while creating the product.
+- Queried a product's brand, and vice versa.
+
+***
+
+## Next Steps: Customize Medusa Admin
+
+Clients, such as the Medusa Admin dashboard, can now use brand-related features, such as creating a brand or setting the brand of a product.
+
+In the next chapters, you'll learn how to customize the Medusa Admin to show a product's brand on its details page, and to show a new page with the list of brands in your store.
+
+
# Guide: Sync Brands from Medusa to CMS
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.
@@ -12935,469 +13411,6 @@ 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.
-
-Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-
-## 1. Create Module Directory
-
-You'll integrate the third-party system in a new CMS Module. So, create the directory `src/modules/cms` that will hold the module's resources.
-
-
-
-***
-
-## 2. Create Module Service
-
-Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
-
-Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
-import { Logger, ConfigModule } from "@medusajs/framework/types"
-
-export type ModuleOptions = {
- apiKey: string
-}
-
-type InjectedDependencies = {
- logger: Logger
- configModule: ConfigModule
-}
-
-class CmsModuleService {
- private options_: ModuleOptions
- private logger_: Logger
-
- constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
- this.logger_ = logger
- this.options_ = options
-
- // TODO initialize SDK
- }
-}
-
-export default CmsModuleService
-```
-
-You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
-
-1. The module's container. Since a module is [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), it has a [local container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md) different than the Medusa container you use in other customizations. This container holds framework tools like the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) and resources within the module.
-2. Options passed to the module when it's later added in Medusa's configurations. These options are useful to pass secret keys or configurations that ensure your module is re-usable across applications. For the CMS Module, you accept the API key to connect to the dummy CMS as an option.
-
-When integrating a third-party system that has a Node.js SDK or client, you can initialize that client in the constructor to be used in the service's methods.
-
-### Integration Methods
-
-Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
-
-Add the following methods in the `CmsModuleService`:
-
-```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
-export class CmsModuleService {
- // ...
-
- // a dummy method to simulate sending a request,
- // in a realistic scenario, you'd use an SDK, fetch, or axios clients
- private async sendRequest(url: string, method: string, data?: any) {
- this.logger_.info(`Sending a ${method} request to ${url}.`)
- this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
- this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
- }
-
- async createBrand(brand: Record) {
- await this.sendRequest("/brands", "POST", brand)
- }
-
- async deleteBrand(id: string) {
- await this.sendRequest(`/brands/${id}`, "DELETE")
- }
-
- async retrieveBrands(): Promise[]> {
- await this.sendRequest("/brands", "GET")
-
- return []
- }
-}
-```
-
-The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
-
-You also add three methods that use the `sendRequest` method:
-
-- `createBrand` that creates a brand in the third-party system.
-- `deleteBrand` that deletes the brand in the third-party system.
-- `retrieveBrands` to retrieve a brand from the third-party system.
-
-***
-
-## 3. Export Module Definition
-
-After creating the module's service, you'll export the module definition indicating the module's name and service.
-
-Create the file `src/modules/cms/index.ts` with the following content:
-
-
-
-```ts title="src/modules/cms/index.ts"
-import { Module } from "@medusajs/framework/utils"
-import CmsModuleService from "./service"
-
-export const CMS_MODULE = "cms"
-
-export default Module(CMS_MODULE, {
- service: CmsModuleService,
-})
-```
-
-You use `Module` from the Modules SDK to export the module's defintion, indicating that the module's name is `cms` and its service is `CmsModuleService`.
-
-***
-
-## 4. Add Module to Medusa's Configurations
-
-Finally, add the module to the Medusa configurations at `medusa-config.ts`:
-
-```ts title="medusa-config.ts"
-module.exports = defineConfig({
- // ...
- modules: [
- // ...
- {
- resolve: "./src/modules/cms",
- options: {
- apiKey: process.env.CMS_API_KEY,
- },
- },
- ],
-})
-```
-
-The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
-
-You can add the `CMS_API_KEY` environment variable to `.env`:
-
-```bash
-CMS_API_KEY=123
-```
-
-***
-
-## Next Steps: Sync Brand From Medusa to CMS
-
-You can now use the CMS Module's service to perform actions on the third-party CMS.
-
-In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service.
-
-
-# Module Container
-
-In this chapter, you'll learn about the module's container and how to resolve resources in that container.
-
-Since modules are isolated, each module has a local container only used by the resources of that module.
-
-So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
-
-### List of Registered Resources
-
-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).
-
-***
-
-## Resolve Resources
-
-### Services
-
-A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container.
-
-For example:
-
-```ts highlights={[["4"], ["10"]]}
-import { Logger } from "@medusajs/framework/types"
-
-type InjectedDependencies = {
- logger: Logger
-}
-
-export default class HelloModuleService {
- protected logger_: Logger
-
- constructor({ logger }: InjectedDependencies) {
- this.logger_ = logger
-
- this.logger_.info("[HelloModuleService]: Hello World!")
- }
-
- // ...
-}
-```
-
-### Loader
-
-A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources.
-
-For example:
-
-```ts highlights={[["9"]]}
-import {
- LoaderOptions,
-} from "@medusajs/framework/types"
-import {
- ContainerRegistrationKeys,
-} from "@medusajs/framework/utils"
-
-export default async function helloWorldLoader({
- container,
-}: LoaderOptions) {
- const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
-
- logger.info("[helloWorldLoader]: Hello, World!")
-}
-```
-
-
-# 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({
- MyCustom,
-}){
- protected eventBusService_: AbstractEventBusModuleService
-
- constructor({ event_bus }) {
- super(...arguments)
- this.eventBusService_ = event_bus
- }
-}
-```
-
-### Without Service Factory
-
-```ts title="src/modules/hello/service.ts" highlights={["6"]}
-import { IEventBusService } from "@medusajs/framework/types"
-
-class HelloModuleService {
- protected eventBusService_: AbstractEventBusModuleService
-
- constructor({ event_bus }) {
- this.eventBusService_ = event_bus
- }
-}
-```
-
-2. Use the event bus service's `emit` method in the service's methods to emit an event:
-
-```ts title="src/modules/hello/service.ts" highlights={serviceHighlights}
-class HelloModuleService {
- // ...
- performAction() {
- // TODO perform action
-
- this.eventBusService_.emit({
- name: "custom.event",
- data: {
- id: "123",
- // other data payload
- },
- })
- }
-}
-```
-
-The method accepts an object having the following properties:
-
-- `name`: 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.
-
-3. By default, the Event Module's service isn't injected into your module's container. To add it to the container, pass it in the module's registration object in `medusa-config.ts` in the `dependencies` property:
-
-```ts title="medusa-config.ts" highlights={depsHighlight}
-import { Modules } from "@medusajs/framework/utils"
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "./src/modules/hello",
- dependencies: [
- Modules.EVENT_BUS,
- ],
- },
- ],
-})
-```
-
-The `dependencies` property accepts an array of module registration keys. The specified modules' main services are injected into the module's container.
-
-That's how you can resolve it in your module's main service's constructor.
-
-### Test it Out
-
-If you execute the `performAction` method of your service, the event is emitted and you can see it in your application's logs.
-
-Any subscribers listening to the event are also executed.
-
-
-# Guide: Define Module Link Between Brand and Product
-
-In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box.
-
-Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links.
-
-A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK.
-
-In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records.
-
-Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md).
-
-### Prerequisites
-
-- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)
-
-## 1. Define Link
-
-Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK.
-
-So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content:
-
-
-
-```ts title="src/links/product-brand.ts" highlights={highlights}
-import BrandModule from "../modules/brand"
-import ProductModule from "@medusajs/medusa/product"
-import { defineLink } from "@medusajs/framework/utils"
-
-export default defineLink(
- {
- linkable: ProductModule.linkable.product,
- isList: true,
- },
- BrandModule.linkable.brand
-)
-```
-
-You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations.
-
-The `defineLink` function accepts two parameters of the same type, which is either:
-
-- The data model's link configuration, which you access from the Module's `linkable` property;
-- Or an object that has two properties:
- - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property.
- - `isList`: A boolean indicating whether many records of the data model can be linked to the other model.
-
-So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object.
-
-***
-
-## 2. Sync the Link to the Database
-
-A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database:
-
-```bash
-npx medusa db:migrate
-```
-
-This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link.
-
-You can also run the `npx medusa db:sync-links` to just sync module links without running migrations.
-
-***
-
-## Next Steps: Extend Create Product Flow
-
-In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records.
-
-
# Guide: Extend Create Product Flow
After linking the [custom Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) in the [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md), you'll extend the create product workflow and API route to allow associating a brand with a product.
@@ -13610,140 +13623,135 @@ In the Medusa application's logs, you'll find the message `Linked brand to produ
Now that you've extending the create-product flow to link a brand to it, you want to retrieve the brand details of a product. You'll learn how to do so in the next chapter.
-# Guide: Query Product's Brands
+# Example: Write Integration Tests for Workflows
-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.
+In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork.
### 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)
+- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
-***
+## Write Integration Test for Workflow
-## Approach 1: Retrieve Brands in Existing API Routes
+Consider you have the following workflow defined at `src/workflows/hello-world.ts`:
-Medusa's existing API routes accept a `fields` query parameter that allows you to specify the fields and relations of a model to retrieve. So, when you send a request to the [List Products](https://docs.medusajs.com/api/admin#products_getproducts), [Get Product](https://docs.medusajs.com/api/admin#products_getproductsid), or any product-related store or admin routes that accept a `fields` query parameter, you can specify in this parameter to return the product's brands.
-
-Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations).
-
-For example, send the following request to retrieve the list of products with their brands:
-
-```bash
-curl 'http://localhost:9000/admin/products?fields=+brand.*' \
---header 'Authorization: Bearer {token}'
-```
-
-Make sure to replace `{token}` with your admin user's authentication token. Learn how to retrieve it in the [API reference](https://docs.medusajs.com/api/store#authentication).
-
-Any product that is linked to a brand will have a `brand` property in its object:
-
-```json title="Example Product Object"
-{
- "id": "prod_123",
- // ...
- "brand": {
- "id": "01JEB44M61BRM3ARM2RRMK7GJF",
- "name": "Acme",
- "created_at": "2024-12-05T09:59:08.737Z",
- "updated_at": "2024-12-05T09:59:08.737Z",
- "deleted_at": null
- }
-}
-```
-
-By using the `fields` query parameter, you don't have to re-create existing API routes to get custom data models that you linked to core data models.
-
-***
-
-## Approach 2: Use Query to Retrieve Linked Records
-
-You can also retrieve linked records using Query. Query allows you to retrieve data across modules with filters, pagination, and more. You can resolve Query from the Medusa container and use it in your API route or workflow.
-
-Learn more about Query in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md).
-
-For example, you can create an API route that retrieves brands and their products. If you followed the [Create Brands API route chapter](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), you'll have the file `src/api/admin/brands/route.ts` with a `POST` API route. Add a new `GET` function to the same file:
-
-```ts title="src/api/admin/brands/route.ts" highlights={highlights}
-// other imports...
+```ts title="src/workflows/hello-world.ts"
import {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
+ createWorkflow,
+ createStep,
+ StepResponse,
+ WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
-export const GET = async (
- req: MedusaRequest,
- res: MedusaResponse
-) => {
- const query = req.scope.resolve("query")
-
- const { data: brands } = await query.graph({
- entity: "brand",
- fields: ["*", "products.*"],
- })
+const step1 = createStep("step-1", () => {
+ return new StepResponse("Hello, World!")
+})
- res.json({ brands })
-}
+export const helloWorldWorkflow = createWorkflow(
+ "hello-world-workflow",
+ () => {
+ const message = step1()
+
+ return new WorkflowResponse(message)
+ }
+)
```
-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:
+To write a test for this workflow, create the file `integration-tests/http/workflow.spec.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.
+```ts title="integration-tests/http/workflow.spec.ts"
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+import { helloWorldWorkflow } from "../../src/workflows/hello-world"
-`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response.
+medusaIntegrationTestRunner({
+ testSuite: ({ getContainer }) => {
+ describe("Test hello-world workflow", () => {
+ it("returns message", async () => {
+ const { result } = await helloWorldWorkflow(getContainer())
+ .run()
-### Test it Out
+ expect(result).toEqual("Hello, World!")
+ })
+ })
+ },
+})
-To test the API route out, send a `GET` request to `/admin/brands`:
-
-```bash
-curl 'http://localhost:9000/admin/brands' \
--H 'Authorization: Bearer {token}'
+jest.setTimeout(60 * 1000)
```
-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).
+You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`.
-This returns the brands in your store with their linked products. For example:
+### Jest Timeout
-```json title="Example Response"
-{
- "brands": [
- {
- "id": "123",
- // ...
- "products": [
- {
- "id": "prod_123",
- // ...
- }
- ]
- }
- ]
-}
+Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+
+```ts title="integration-tests/http/custom-routes.spec.ts"
+// in your test's file
+jest.setTimeout(60 * 1000)
```
***
-## Summary
+## Run Test
-By following the examples of the previous chapters, you:
+Run the following command to run your tests:
-- 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.
+```bash npm2yarn
+npm run test:integration
+```
+
+If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
+
+This runs your Medusa application and runs the tests available under the `integrations/http` directory.
***
-## Next Steps: Customize Medusa Admin
+## Test That a Workflow Throws an Error
-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.
+You might want to test that a workflow throws an error in certain cases. To test this:
-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.
+- Disable the `throwOnError` option when executing the workflow.
+- Use the returned `errors` property to check what errors were thrown.
+
+For example, if you have a step that throws this error:
+
+```ts title="src/workflows/hello-world.ts"
+import { MedusaError } from "@medusajs/framework/utils"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+
+const step1 = createStep("step-1", () => {
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist")
+})
+```
+
+You can write the following test to ensure that the workflow throws that error:
+
+```ts title="integration-tests/http/workflow.spec.ts"
+import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
+import { helloWorldWorkflow } from "../../src/workflows/hello-world"
+
+medusaIntegrationTestRunner({
+ testSuite: ({ getContainer }) => {
+ describe("Test hello-world workflow", () => {
+ it("returns message", async () => {
+ const { errors } = await helloWorldWorkflow(getContainer())
+ .run({
+ throwOnError: false,
+ })
+
+ expect(errors.length).toBeGreaterThan(0)
+ expect(errors[0].error.message).toBe("Item doesn't exist")
+ })
+ })
+ },
+})
+
+jest.setTimeout(60 * 1000)
+```
+
+The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown.
+
+If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`.
# Example: Write Integration Tests for API Routes
@@ -14312,135 +14320,163 @@ const response = await api.post(`/custom`, form, {
```
-# Example: Write Integration Tests for Workflows
+# Guide: Integrate CMS Brand System
-In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork.
+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.
-### Prerequisites
+Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md).
-- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md)
+## 1. Create Module Directory
-## Write Integration Test for Workflow
+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.
-Consider you have the following workflow defined at `src/workflows/hello-world.ts`:
+
-```ts title="src/workflows/hello-world.ts"
-import {
- createWorkflow,
- createStep,
- StepResponse,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
+***
-const step1 = createStep("step-1", () => {
- return new StepResponse("Hello, World!")
-})
+## 2. Create Module Service
-export const helloWorldWorkflow = createWorkflow(
- "hello-world-workflow",
- () => {
- const message = step1()
+Next, you'll create the module's service. It will provide methods to connect and perform actions with the third-party system.
- return new WorkflowResponse(message)
+Create the CMS Module's service at `src/modules/cms/service.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/service.ts" highlights={serviceHighlights}
+import { Logger, ConfigModule } from "@medusajs/framework/types"
+
+export type ModuleOptions = {
+ apiKey: string
+}
+
+type InjectedDependencies = {
+ logger: Logger
+ configModule: ConfigModule
+}
+
+class CmsModuleService {
+ private options_: ModuleOptions
+ private logger_: Logger
+
+ constructor({ logger }: InjectedDependencies, options: ModuleOptions) {
+ this.logger_ = logger
+ this.options_ = options
+
+ // TODO initialize SDK
}
-)
+}
+
+export default CmsModuleService
```
-To write a test for this workflow, create the file `integration-tests/http/workflow.spec.ts` with the following content:
+You create a `CmsModuleService` that will hold the methods to connect to the third-party CMS. A service's constructor accepts two parameters:
-```ts title="integration-tests/http/workflow.spec.ts"
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-import { helloWorldWorkflow } from "../../src/workflows/hello-world"
+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.
-medusaIntegrationTestRunner({
- testSuite: ({ getContainer }) => {
- describe("Test hello-world workflow", () => {
- it("returns message", async () => {
- const { result } = await helloWorldWorkflow(getContainer())
- .run()
+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.
- expect(result).toEqual("Hello, World!")
- })
- })
- },
+### Integration Methods
+
+Next, you'll add methods that simulate sending requests to a third-party CMS. You'll use these methods later to sync brands from and to the CMS.
+
+Add the following methods in the `CmsModuleService`:
+
+```ts title="src/modules/cms/service.ts" highlights={methodsHighlights}
+export class CmsModuleService {
+ // ...
+
+ // a dummy method to simulate sending a request,
+ // in a realistic scenario, you'd use an SDK, fetch, or axios clients
+ private async sendRequest(url: string, method: string, data?: any) {
+ this.logger_.info(`Sending a ${method} request to ${url}.`)
+ this.logger_.info(`Request Data: ${JSON.stringify(data, null, 2)}`)
+ this.logger_.info(`API Key: ${JSON.stringify(this.options_.apiKey, null, 2)}`)
+ }
+
+ async createBrand(brand: Record) {
+ await this.sendRequest("/brands", "POST", brand)
+ }
+
+ async deleteBrand(id: string) {
+ await this.sendRequest(`/brands/${id}`, "DELETE")
+ }
+
+ async retrieveBrands(): Promise[]> {
+ await this.sendRequest("/brands", "GET")
+
+ return []
+ }
+}
+```
+
+The `sendRequest` method sends requests to the third-party CMS. Since this guide isn't using a real CMS, it only simulates the sending by logging messages in the terminal.
+
+You also add three methods that use the `sendRequest` method:
+
+- `createBrand` that creates a brand in the third-party system.
+- `deleteBrand` that deletes the brand in the third-party system.
+- `retrieveBrands` to retrieve a brand from the third-party system.
+
+***
+
+## 3. Export Module Definition
+
+After creating the module's service, you'll export the module definition indicating the module's name and service.
+
+Create the file `src/modules/cms/index.ts` with the following content:
+
+
+
+```ts title="src/modules/cms/index.ts"
+import { Module } from "@medusajs/framework/utils"
+import CmsModuleService from "./service"
+
+export const CMS_MODULE = "cms"
+
+export default Module(CMS_MODULE, {
+ service: CmsModuleService,
})
-
-jest.setTimeout(60 * 1000)
```
-You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`.
+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`.
-### Jest Timeout
+***
-Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test:
+## 4. Add Module to Medusa's Configurations
-```ts title="integration-tests/http/custom-routes.spec.ts"
-// in your test's file
-jest.setTimeout(60 * 1000)
+Finally, add the module to the Medusa configurations at `medusa-config.ts`:
+
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ // ...
+ modules: [
+ // ...
+ {
+ resolve: "./src/modules/cms",
+ options: {
+ apiKey: process.env.CMS_API_KEY,
+ },
+ },
+ ],
+})
+```
+
+The object passed in `modules` accept an `options` property, whose value is an object of options to pass to the module. These are the options you receive in the `CmsModuleService`'s constructor.
+
+You can add the `CMS_API_KEY` environment variable to `.env`:
+
+```bash
+CMS_API_KEY=123
```
***
-## Run Test
+## Next Steps: Sync Brand From Medusa to CMS
-Run the following command to run your tests:
+You can now use the CMS Module's service to perform actions on the third-party CMS.
-```bash npm2yarn
-npm run test:integration
-```
-
-If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md).
-
-This runs your Medusa application and runs the tests available under the `integrations/http` directory.
-
-***
-
-## Test That a Workflow Throws an Error
-
-You might want to test that a workflow throws an error in certain cases. To test this:
-
-- Disable the `throwOnError` option when executing the workflow.
-- Use the returned `errors` property to check what errors were thrown.
-
-For example, if you have a step that throws this error:
-
-```ts title="src/workflows/hello-world.ts"
-import { MedusaError } from "@medusajs/framework/utils"
-import { createStep } from "@medusajs/framework/workflows-sdk"
-
-const step1 = createStep("step-1", () => {
- throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist")
-})
-```
-
-You can write the following test to ensure that the workflow throws that error:
-
-```ts title="integration-tests/http/workflow.spec.ts"
-import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-import { helloWorldWorkflow } from "../../src/workflows/hello-world"
-
-medusaIntegrationTestRunner({
- testSuite: ({ getContainer }) => {
- describe("Test hello-world workflow", () => {
- it("returns message", async () => {
- const { errors } = await helloWorldWorkflow(getContainer())
- .run({
- throwOnError: false,
- })
-
- expect(errors.length).toBeGreaterThan(0)
- expect(errors[0].error.message).toBe("Item doesn't exist")
- })
- })
- },
-})
-
-jest.setTimeout(60 * 1000)
-```
-
-The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown.
-
-If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`.
+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.
# Example: Integration Tests for a Module
@@ -14534,6 +14570,136 @@ The Commerce Modules can be used in many use cases, including:
- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
+# Auth Module
+
+In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
+
+Medusa has auth related features available out-of-the-box through the Auth Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Auth Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Auth Features
+
+- [Basic User Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#1-basic-authentication-flow/index.html.md): Authenticate users using their email and password credentials.
+- [Third-Party and Social Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md): Authenticate users using third-party services and social platforms, such as [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) and [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md).
+- [Authenticate Custom Actor Types](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md): Create custom user or actor types, such as managers, authenticate them in your application, and guard routes based on the custom user types.
+- [Custom Authentication Providers](https://docs.medusajs.com/references/auth/provider/index.html.md): Integrate third-party services with custom authentication providors.
+
+***
+
+## How to Use the Auth Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules, MedusaError } from "@medusajs/framework/utils"
+import { MedusaRequest } from "@medusajs/framework/http"
+import { AuthenticationInput } from "@medusajs/framework/types"
+
+type Input = {
+ req: MedusaRequest
+}
+
+const authenticateUserStep = createStep(
+ "authenticate-user",
+ async ({ req }: Input, { container }) => {
+ const authModuleService = container.resolve(Modules.AUTH)
+
+ const { success, authIdentity, error } = await authModuleService
+ .authenticate(
+ "emailpass",
+ {
+ url: req.url,
+ headers: req.headers,
+ query: req.query,
+ body: req.body,
+ authScope: "admin", // or custom actor type
+ protocol: req.protocol,
+ } as AuthenticationInput
+ )
+
+ if (!success) {
+ // incorrect authentication details
+ throw new MedusaError(
+ MedusaError.Types.UNAUTHORIZED,
+ error || "Incorrect authentication details"
+ )
+ }
+
+ return new StepResponse({ authIdentity }, authIdentity?.id)
+ },
+ async (authIdentityId, { container }) => {
+ if (!authIdentityId) {
+ return
+ }
+
+ const authModuleService = container.resolve(Modules.AUTH)
+
+ await authModuleService.deleteAuthIdentities([authIdentityId])
+ }
+)
+
+export const authenticateUserWorkflow = createWorkflow(
+ "authenticate-user",
+ (input: Input) => {
+ const { authIdentity } = authenticateUserStep(input)
+
+ return new WorkflowResponse({
+ authIdentity,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await authenticateUserWorkflow(req.scope)
+ .run({
+ req,
+ })
+
+ res.send(result)
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+## Configure Auth Module
+
+The Auth Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/module-options/index.html.md) for details on the module's options.
+
+***
+
+## Providers
+
+Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
+
+***
+
+
# API Key Module
In this section of the documentation, you will find resources to learn more about the API Key Module and how to use it in your application.
@@ -14822,22 +14988,25 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Currency Module
+# Inventory 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 Inventory Module and how to use it in your application.
-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.
+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).
-## Currency Features
+## Inventory 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.
+- [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 Currency 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.
@@ -14845,46 +15014,45 @@ 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-inventory-item.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 createInventoryItemStep = createStep(
+ "create-inventory-item",
async ({}, { container }) => {
- const currencyModuleService = container.resolve(Modules.CURRENCY)
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
- const currency = await currencyModuleService
- .retrieveCurrency("usd")
+ const inventoryItem = await inventoryModuleService.createInventoryItems({
+ sku: "SHIRT",
+ title: "Green Medusa Shirt",
+ requires_shipping: true,
+ })
- return new StepResponse({ currency })
+ return new StepResponse({ inventoryItem }, inventoryItem.id)
+ },
+ async (inventoryItemId, { container }) => {
+ if (!inventoryItemId) {
+ return
+ }
+ const inventoryModuleService = container.resolve(Modules.INVENTORY)
+
+ await inventoryModuleService.deleteInventoryItems([inventoryItemId])
}
)
-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 createInventoryItemWorkflow = createWorkflow(
+ "create-inventory-item-workflow",
+ () => {
+ const { inventoryItem } = createInventoryItemStep()
return new WorkflowResponse({
- formattedPrice,
+ inventoryItem,
})
}
)
@@ -14894,21 +15062,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 { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
- const { result } = await retrievePriceWithCurrency(req.scope)
- .run({
- price: 10,
- })
+ const { result } = await createInventoryItemWorkflow(req.scope)
+ .run()
res.send(result)
}
@@ -14916,21 +15082,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 { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
export default async function handleUserCreated({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
+ const { result } = await createInventoryItemWorkflow(container)
+ .run()
console.log(result)
}
@@ -14942,17 +15106,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 { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
export default async function myCustomJob(
container: MedusaContainer
) {
- const { result } = await retrievePriceWithCurrency(container)
- .run({
- price: 10,
- })
+ const { result } = await createInventoryItemWorkflow(container)
+ .run()
console.log(result)
}
@@ -15129,148 +15291,6 @@ The Fulfillment Module accepts options for further configurations. Refer to [thi
***
-# 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.
-
-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).
-
-## Inventory 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.
-
-***
-
-## 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.
-
-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-inventory-item.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createInventoryItemStep = createStep(
- "create-inventory-item",
- async ({}, { container }) => {
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
-
- const inventoryItem = await inventoryModuleService.createInventoryItems({
- sku: "SHIRT",
- title: "Green Medusa Shirt",
- requires_shipping: true,
- })
-
- return new StepResponse({ inventoryItem }, inventoryItem.id)
- },
- async (inventoryItemId, { container }) => {
- if (!inventoryItemId) {
- return
- }
- const inventoryModuleService = container.resolve(Modules.INVENTORY)
-
- await inventoryModuleService.deleteInventoryItems([inventoryItemId])
- }
-)
-
-export const createInventoryItemWorkflow = createWorkflow(
- "create-inventory-item-workflow",
- () => {
- const { inventoryItem } = createInventoryItemStep()
-
- return new WorkflowResponse({
- inventoryItem,
- })
- }
-)
-```
-
-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 { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createInventoryItemWorkflow(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 { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createInventoryItemWorkflow(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 { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createInventoryItemWorkflow(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).
-
-***
-
-
# 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.
@@ -15410,6 +15430,458 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+# Order Module
+
+In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
+
+Medusa has order related features available out-of-the-box through the Order Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Order Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Order Features
+
+- [Order Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/concepts/index.html.md): Store and manage your orders to retrieve, create, cancel, and perform other operations.
+- Draft Orders: Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
+- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/promotion-adjustments/index.html.md): Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
+- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/tax-lines/index.html.md): Apply tax lines to an order's line items and shipping methods.
+- [Returns](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md), [Edits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md), [Exchanges](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md), and [Claims](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md): Make [changes](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-change/index.html.md) to an order to edit, return, or exchange its items, with [version-based control](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-versioning/index.html.md) over the order's timeline.
+
+***
+
+## How to Use the Order Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+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-draft-order.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createDraftOrderStep = createStep(
+ "create-order",
+ async ({}, { container }) => {
+ const orderModuleService = container.resolve(Modules.ORDER)
+
+ const draftOrder = await orderModuleService.createOrders({
+ currency_code: "usd",
+ items: [
+ {
+ title: "Shirt",
+ quantity: 1,
+ unit_price: 3000,
+ },
+ ],
+ shipping_methods: [
+ {
+ name: "Express shipping",
+ amount: 3000,
+ },
+ ],
+ status: "draft",
+ })
+
+ return new StepResponse({ draftOrder }, draftOrder.id)
+ },
+ async (draftOrderId, { container }) => {
+ if (!draftOrderId) {
+ return
+ }
+ const orderModuleService = container.resolve(Modules.ORDER)
+
+ await orderModuleService.deleteOrders([draftOrderId])
+ }
+)
+
+export const createDraftOrderWorkflow = createWorkflow(
+ "create-draft-order",
+ () => {
+ const { draftOrder } = createDraftOrderStep()
+
+ return new WorkflowResponse({
+ draftOrder,
+ })
+ }
+)
+```
+
+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 { createDraftOrderWorkflow } from "../../workflows/create-draft-order"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createDraftOrderWorkflow(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 { createDraftOrderWorkflow } from "../workflows/create-draft-order"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createDraftOrderWorkflow(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 { createDraftOrderWorkflow } from "../workflows/create-draft-order"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createDraftOrderWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
+# Pricing Module
+
+In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
+
+Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Pricing Features
+
+- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
+- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
+- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
+- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
+- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
+
+***
+
+## How to Use the Pricing Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+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-price-set.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createPriceSetStep = createStep(
+ "create-price-set",
+ async ({}, { container }) => {
+ const pricingModuleService = container.resolve(Modules.PRICING)
+
+ const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
+ {
+ amount: 500,
+ currency_code: "USD",
+ },
+ {
+ amount: 400,
+ currency_code: "EUR",
+ min_quantity: 0,
+ max_quantity: 4,
+ rules: {},
+ },
+ ],
+ })
+
+ return new StepResponse({ priceSet }, priceSet.id)
+ },
+ async (priceSetId, { container }) => {
+ if (!priceSetId) {
+ return
+ }
+ const pricingModuleService = container.resolve(Modules.PRICING)
+
+ await pricingModuleService.deletePriceSets([priceSetId])
+ }
+)
+
+export const createPriceSetWorkflow = createWorkflow(
+ "create-price-set",
+ () => {
+ const { priceSet } = createPriceSetStep()
+
+ return new WorkflowResponse({
+ priceSet,
+ })
+ }
+)
+```
+
+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 { createPriceSetWorkflow } from "../../workflows/create-price-set"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createPriceSetWorkflow(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 { createPriceSetWorkflow } from "../workflows/create-price-set"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createPriceSetWorkflow(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 { createPriceSetWorkflow } from "../workflows/create-price-set"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createPriceSetWorkflow(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).
+
+***
+
+
+# Currency Module
+
+In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
+
+Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Currency Features
+
+- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them.
+- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
+
+***
+
+## How to Use the Currency Module
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+ transform,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const retrieveCurrencyStep = createStep(
+ "retrieve-currency",
+ async ({}, { container }) => {
+ const currencyModuleService = container.resolve(Modules.CURRENCY)
+
+ const currency = await currencyModuleService
+ .retrieveCurrency("usd")
+
+ return new StepResponse({ currency })
+ }
+)
+
+type Input = {
+ price: number
+}
+
+export const retrievePriceWithCurrency = createWorkflow(
+ "create-currency",
+ (input: Input) => {
+ const { currency } = retrieveCurrencyStep()
+
+ const formattedPrice = transform({
+ input,
+ currency,
+ }, (data) => {
+ return `${data.currency.symbol}${data.input.price}`
+ })
+
+ return new WorkflowResponse({
+ formattedPrice,
+ })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await retrievePriceWithCurrency(req.scope)
+ .run({
+ price: 10,
+ })
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await retrievePriceWithCurrency(container)
+ .run({
+ price: 10,
+ })
+
+ console.log(result)
+}
+
+export const config = {
+ name: "run-once-a-day",
+ schedule: `0 0 * * *`,
+}
+```
+
+Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+
+***
+
+
# 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.
@@ -15563,136 +16035,6 @@ Medusa provides the following payment providers out-of-the-box. You can use them
***
-# Auth Module
-
-In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
-
-Medusa has auth related features available out-of-the-box through the Auth Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Auth Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Auth Features
-
-- [Basic User Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#1-basic-authentication-flow/index.html.md): Authenticate users using their email and password credentials.
-- [Third-Party and Social Authentication](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md): Authenticate users using third-party services and social platforms, such as [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) and [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md).
-- [Authenticate Custom Actor Types](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md): Create custom user or actor types, such as managers, authenticate them in your application, and guard routes based on the custom user types.
-- [Custom Authentication Providers](https://docs.medusajs.com/references/auth/provider/index.html.md): Integrate third-party services with custom authentication providors.
-
-***
-
-## How to Use the Auth Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
-
-For example:
-
-```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules, MedusaError } from "@medusajs/framework/utils"
-import { MedusaRequest } from "@medusajs/framework/http"
-import { AuthenticationInput } from "@medusajs/framework/types"
-
-type Input = {
- req: MedusaRequest
-}
-
-const authenticateUserStep = createStep(
- "authenticate-user",
- async ({ req }: Input, { container }) => {
- const authModuleService = container.resolve(Modules.AUTH)
-
- const { success, authIdentity, error } = await authModuleService
- .authenticate(
- "emailpass",
- {
- url: req.url,
- headers: req.headers,
- query: req.query,
- body: req.body,
- authScope: "admin", // or custom actor type
- protocol: req.protocol,
- } as AuthenticationInput
- )
-
- if (!success) {
- // incorrect authentication details
- throw new MedusaError(
- MedusaError.Types.UNAUTHORIZED,
- error || "Incorrect authentication details"
- )
- }
-
- return new StepResponse({ authIdentity }, authIdentity?.id)
- },
- async (authIdentityId, { container }) => {
- if (!authIdentityId) {
- return
- }
-
- const authModuleService = container.resolve(Modules.AUTH)
-
- await authModuleService.deleteAuthIdentities([authIdentityId])
- }
-)
-
-export const authenticateUserWorkflow = createWorkflow(
- "authenticate-user",
- (input: Input) => {
- const { authIdentity } = authenticateUserStep(input)
-
- return new WorkflowResponse({
- authIdentity,
- })
- }
-)
-```
-
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
-
-```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await authenticateUserWorkflow(req.scope)
- .run({
- req,
- })
-
- res.send(result)
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-## Configure Auth Module
-
-The Auth Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/module-options/index.html.md) for details on the module's options.
-
-***
-
-## Providers
-
-Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
-
-***
-
-
# 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.
@@ -15991,312 +16333,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
-# Pricing Module
-
-In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
-
-Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Pricing Features
-
-- [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant.
-- [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts.
-- [Price Lists](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts#price-list/index.html.md): Group prices and apply them only in specific conditions with price lists.
-- [Price Calculation Strategy](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md): Retrieve the best price in a given context and for the specified rule values.
-- [Tax-Inclusive Pricing](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/index.html.md): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
-
-***
-
-## How to Use the Pricing Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-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-price-set.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createPriceSetStep = createStep(
- "create-price-set",
- async ({}, { container }) => {
- const pricingModuleService = container.resolve(Modules.PRICING)
-
- const priceSet = await pricingModuleService.createPriceSets({
- prices: [
- {
- amount: 500,
- currency_code: "USD",
- },
- {
- amount: 400,
- currency_code: "EUR",
- min_quantity: 0,
- max_quantity: 4,
- rules: {},
- },
- ],
- })
-
- return new StepResponse({ priceSet }, priceSet.id)
- },
- async (priceSetId, { container }) => {
- if (!priceSetId) {
- return
- }
- const pricingModuleService = container.resolve(Modules.PRICING)
-
- await pricingModuleService.deletePriceSets([priceSetId])
- }
-)
-
-export const createPriceSetWorkflow = createWorkflow(
- "create-price-set",
- () => {
- const { priceSet } = createPriceSetStep()
-
- return new WorkflowResponse({
- priceSet,
- })
- }
-)
-```
-
-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 { createPriceSetWorkflow } from "../../workflows/create-price-set"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createPriceSetWorkflow(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 { createPriceSetWorkflow } from "../workflows/create-price-set"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createPriceSetWorkflow(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 { createPriceSetWorkflow } from "../workflows/create-price-set"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createPriceSetWorkflow(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).
-
-***
-
-
-# Order Module
-
-In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
-
-Medusa has order related features available out-of-the-box through the Order Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Order Module.
-
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
-
-## Order Features
-
-- [Order Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/concepts/index.html.md): Store and manage your orders to retrieve, create, cancel, and perform other operations.
-- Draft Orders: Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
-- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/promotion-adjustments/index.html.md): Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
-- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/tax-lines/index.html.md): Apply tax lines to an order's line items and shipping methods.
-- [Returns](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md), [Edits](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md), [Exchanges](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md), and [Claims](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md): Make [changes](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-change/index.html.md) to an order to edit, return, or exchange its items, with [version-based control](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/order-versioning/index.html.md) over the order's timeline.
-
-***
-
-## How to Use the Order Module
-
-In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
-
-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-draft-order.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
-
-const createDraftOrderStep = createStep(
- "create-order",
- async ({}, { container }) => {
- const orderModuleService = container.resolve(Modules.ORDER)
-
- const draftOrder = await orderModuleService.createOrders({
- currency_code: "usd",
- items: [
- {
- title: "Shirt",
- quantity: 1,
- unit_price: 3000,
- },
- ],
- shipping_methods: [
- {
- name: "Express shipping",
- amount: 3000,
- },
- ],
- status: "draft",
- })
-
- return new StepResponse({ draftOrder }, draftOrder.id)
- },
- async (draftOrderId, { container }) => {
- if (!draftOrderId) {
- return
- }
- const orderModuleService = container.resolve(Modules.ORDER)
-
- await orderModuleService.deleteOrders([draftOrderId])
- }
-)
-
-export const createDraftOrderWorkflow = createWorkflow(
- "create-draft-order",
- () => {
- const { draftOrder } = createDraftOrderStep()
-
- return new WorkflowResponse({
- draftOrder,
- })
- }
-)
-```
-
-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 { createDraftOrderWorkflow } from "../../workflows/create-draft-order"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createDraftOrderWorkflow(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 { createDraftOrderWorkflow } from "../workflows/create-draft-order"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createDraftOrderWorkflow(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 { createDraftOrderWorkflow } from "../workflows/create-draft-order"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createDraftOrderWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
-
-
# Sales Channel Module
In this section of the documentation, you will find resources to learn more about the Sales Channel Module and how to use it in your application.
@@ -16455,6 +16491,280 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc
***
+# Stock Location Module
+
+In this section of the documentation, you will find resources to learn more about the Stock Location Module and how to use it in your application.
+
+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).
+
+## 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.
+
+***
+
+## 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.
+
+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-stock-location.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createStockLocationStep = createStep(
+ "create-stock-location",
+ async ({}, { container }) => {
+ const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
+
+ const stockLocation = await stockLocationModuleService.createStockLocations({
+ name: "Warehouse 1",
+ })
+
+ return new StepResponse({ stockLocation }, stockLocation.id)
+ },
+ async (stockLocationId, { container }) => {
+ if (!stockLocationId) {
+ return
+ }
+ const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
+
+ await stockLocationModuleService.deleteStockLocations([stockLocationId])
+ }
+)
+
+export const createStockLocationWorkflow = createWorkflow(
+ "create-stock-location",
+ () => {
+ const { stockLocation } = createStockLocationStep()
+
+ return new WorkflowResponse({ stockLocation })
+ }
+)
+```
+
+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 { createStockLocationWorkflow } from "../../workflows/create-stock-location"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createStockLocationWorkflow(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 { createStockLocationWorkflow } from "../workflows/create-stock-location"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createStockLocationWorkflow(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 { createStockLocationWorkflow } from "../workflows/create-stock-location"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createStockLocationWorkflow(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).
+
+***
+
+
+# Store Module
+
+In this section of the documentation, you will find resources to learn more about the Store Module and how to use it in your application.
+
+Medusa has store related features available out-of-the-box through the Store Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Store Module.
+
+Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+
+## Store Features
+
+- [Store Management](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create and manage stores in your application.
+- [Multi-Tenancy Support](https://docs.medusajs.com/references/store/models/Store/index.html.md): Create multiple stores, each having its own configurations.
+
+***
+
+## How to Use Store Module's Service
+
+In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
+
+You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
+
+For example:
+
+```ts title="src/workflows/create-store.ts" highlights={highlights}
+import {
+ createWorkflow,
+ WorkflowResponse,
+ createStep,
+ StepResponse,
+} from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const createStoreStep = createStep(
+ "create-store",
+ async ({}, { container }) => {
+ const storeModuleService = container.resolve(Modules.STORE)
+
+ const store = await storeModuleService.createStores({
+ name: "My Store",
+ supported_currencies: [{
+ currency_code: "usd",
+ is_default: true,
+ }],
+ })
+
+ return new StepResponse({ store }, store.id)
+ },
+ async (storeId, { container }) => {
+ if(!storeId) {
+ return
+ }
+ const storeModuleService = container.resolve(Modules.STORE)
+
+ await storeModuleService.deleteStores([storeId])
+ }
+)
+
+export const createStoreWorkflow = createWorkflow(
+ "create-store",
+ () => {
+ const { store } = createStoreStep()
+
+ return new WorkflowResponse({ store })
+ }
+)
+```
+
+You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+
+### API Route
+
+```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import type {
+ MedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { createStoreWorkflow } from "../../workflows/create-store"
+
+export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+) {
+ const { result } = await createStoreWorkflow(req.scope)
+ .run()
+
+ res.send(result)
+}
+```
+
+### Subscriber
+
+```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
+ type SubscriberConfig,
+ type SubscriberArgs,
+} from "@medusajs/framework"
+import { createStoreWorkflow } from "../workflows/create-store"
+
+export default async function handleUserCreated({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ const { result } = await createStoreWorkflow(container)
+ .run()
+
+ console.log(result)
+}
+
+export const config: SubscriberConfig = {
+ event: "user.created",
+}
+```
+
+### Scheduled Job
+
+```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
+import { MedusaContainer } from "@medusajs/framework/types"
+import { createStoreWorkflow } from "../workflows/create-store"
+
+export default async function myCustomJob(
+ container: MedusaContainer
+) {
+ const { result } = await createStoreWorkflow(container)
+ .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).
+
+***
+
+
# Region 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.
@@ -16741,6 +17051,277 @@ The User Module accepts options for further configurations. Refer to [this docum
***
+# 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).
+
+
+# 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.
+
+
# Tax 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.
@@ -16883,315 +17464,992 @@ The Tax Module accepts options for further configurations. Refer to [this docume
***
-# Store Module
+# Auth Providers
-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 document, you’ll learn how the Auth Module handles authentication using providers.
-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.
+## What's an Auth Module Provider?
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service.
-## Store Features
+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.
-- [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.
+### 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)
***
-## How to Use Store Module's Service
+## Configure Allowed Auth Providers of Actor Types
-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.
+By default, users of all actor types can authenticate with all installed auth module providers.
-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.
+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:
-For example:
+```ts title="medusa-config.ts"
+module.exports = defineConfig({
+ projectConfig: {
+ http: {
+ authMethodsPerActor: {
+ user: ["google"],
+ customer: ["emailpass"],
+ },
+ // ...
+ },
+ // ...
+ },
+})
+```
-```ts title="src/workflows/create-store.ts" highlights={highlights}
+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.
+
+
+# How to Use Authentication Routes
+
+In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password.
+
+These routes are added by Medusa's HTTP layer, not the Auth Module.
+
+## Types of Authentication Flows
+
+### 1. Basic Authentication Flow
+
+This authentication flow doesn't require validation with third-party services.
+
+[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md).
+
+The steps are:
+
+
+
+1. Register the user with the [Register Route](#register-route).
+2. Use the authentication token to create the user with their respective API route.
+ - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+ - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
+3. Authenticate the user with the [Auth Route](#login-route).
+
+After registration, you only use the [Auth Route](#login-route) for subsequent authentication.
+
+To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
+
+### 2. Third-Party Service Authenticate Flow
+
+This authentication flow authenticates the user with a third-party service, such as Google.
+
+[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
+
+It requires the following steps:
+
+
+
+1. Authenticate the user with the [Auth Route](#login-route).
+2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location.
+3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication.
+4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters.
+5. If the callback validation is successful, the frontend receives the authentication token.
+6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
+ - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
+ - If not, follow the rest of the steps.
+7. The frontend uses the authentication token to create the user with their respective API route.
+ - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+ - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
+8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated.
+
+***
+
+## Register Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com"
+ // ...
+}'
+```
+
+This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead.
+
+For example, if you're registering a customer, you:
+
+1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token.
+2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication).
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+### Request Body Parameters
+
+This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
+
+For example, the EmailPass provider requires an `email` and `password` fields in the request body.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+### Handling Existing Identities
+
+An auth identity with the same email may already exist in Medusa. This can happen if:
+
+- Another actor type is using that email. For example, an admin user is trying to register as a customer.
+- The same email belongs to a record of the same actor type. For example, another customer has the same email.
+
+In these scenarios, the Register Route will return an error instead of a token:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Identity with email already exists"
+}
+```
+
+To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer.
+
+Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error:
+
+```json
+{
+ "type": "unauthorized",
+ "message": "Invalid email or password"
+}
+```
+
+You can show that error message to the customer.
+
+***
+
+## Login Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com"
+ // ...
+}'
+```
+
+For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`.
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+### Request Body Parameters
+
+This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
+
+For example, the EmailPass provider requires an `email` and `password` fields in the request body.
+
+#### Overriding Callback URL
+
+For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations.
+
+This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+If the authentication requires more action with a third-party service, you'll receive a `location` property:
+
+```json
+{
+ "location": "https://..."
+}
+```
+
+Redirect to that URL in the frontend to continue the authentication process with the third-party service.
+
+[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md).
+
+***
+
+## Validate Callback Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456
+```
+
+Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow.
+
+### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `google`.
+
+### Query Parameters
+
+This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters.
+
+### Response Fields
+
+If the authentication is successful, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt):
+
+- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests.
+- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
+
+***
+
+## Refresh Token Route
+
+The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information.
+
+It requires the user's JWT token that they received from the authentication or callback routes.
+
+```bash
+curl -X POST http://localhost:9000/auth/token/refresh \
+-H 'Authorization: Bearer {token}'
+```
+
+### Response Fields
+
+If the token was refreshed successfully, you'll receive a `token` field in the response body object:
+
+```json
+{
+ "token": "..."
+}
+```
+
+Use that token in the header of subsequent requests to send authenticated requests.
+
+***
+
+## Reset Password Routes
+
+To reset a user's password:
+
+1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route).
+ - The API route emits the `auth.password_reset` event, passing the token in the payload.
+ - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user.
+2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password.
+ - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route.
+
+[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
+
+### Generate Reset Password Token Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "identifier": "Whitney_Schultz@gmail.com"
+}'
+```
+
+This API route is useful for providers like `emailpass` that store a user's password and use it for authentication.
+
+#### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+#### Request Body Parameters
+
+This route accepts in the request body an object having the following property:
+
+- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email.
+
+#### Response Fields
+
+If the authentication is successful, the request returns a `201` response code.
+
+### Reset Password Route
+
+The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password.
+
+```bash
+curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update?token=123
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "Whitney_Schultz@gmail.com",
+ "password": "supersecret"
+}'
+```
+
+This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in.
+
+#### Path Parameters
+
+Its path parameters are:
+
+- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
+- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
+
+#### Query Parameters
+
+The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route).
+
+### Request Body Parameters
+
+This route accepts in the request body an object that has the data necessary for the provider to update the user's password.
+
+For the `emailpass` provider, you must pass the following properties:
+
+- `email`: The user's email.
+- `password`: The new password.
+
+### Response Fields
+
+If the authentication is successful, the request returns an object with a `success` property set to `true`:
+
+```json
+{
+ "success": "true"
+}
+```
+
+
+# How to Create an Actor Type
+
+In this document, learn how to create an actor type and authenticate its associated data model.
+
+## 0. Create Module with Data Model
+
+Before creating an actor type, you must have a module with a data model representing the actor type.
+
+Learn how to create a module in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
+
+The rest of this guide uses this `Manager` data model as an example:
+
+```ts title="src/modules/manager/models/manager.ts"
+import { model } from "@medusajs/framework/utils"
+
+const Manager = model.define("manager", {
+ id: model.id().primaryKey(),
+ firstName: model.text(),
+ lastName: model.text(),
+ email: model.text(),
+})
+
+export default Manager
+```
+
+***
+
+## 1. Create Workflow
+
+Start by creating a workflow that does two things:
+
+- Creates a record of the `Manager` data model.
+- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type.
+
+For example, create the file `src/workflows/create-manager.ts`. with the following content:
+
+```ts title="src/workflows/create-manager.ts" highlights={workflowHighlights}
import {
createWorkflow,
+ createStep,
+ StepResponse,
WorkflowResponse,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ setAuthAppMetadataStep,
+} from "@medusajs/medusa/core-flows"
+import ManagerModuleService from "../modules/manager/service"
+
+type CreateManagerWorkflowInput = {
+ manager: {
+ first_name: string
+ last_name: string
+ email: string
+ }
+ authIdentityId: string
+}
+
+const createManagerStep = createStep(
+ "create-manager-step",
+ async ({
+ manager: managerData,
+ }: Pick,
+ { container }) => {
+ const managerModuleService: ManagerModuleService =
+ container.resolve("managerModuleService")
+
+ const manager = await managerModuleService.createManager(
+ managerData
+ )
+
+ return new StepResponse(manager)
+ }
+)
+
+const createManagerWorkflow = createWorkflow(
+ "create-manager",
+ function (input: CreateManagerWorkflowInput) {
+ const manager = createManagerStep({
+ manager: input.manager,
+ })
+
+ setAuthAppMetadataStep({
+ authIdentityId: input.authIdentityId,
+ actorType: "manager",
+ value: manager.id,
+ })
+
+ return new WorkflowResponse(manager)
+ }
+)
+
+export default createManagerWorkflow
+```
+
+This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved.
+
+The workflow has two steps:
+
+1. Create the manager using the `createManagerStep`.
+2. Set the `app_metadata` property of the associated auth identity using the `setAuthAppMetadataStep` from Medusa's core workflows. You specify the actor type `manager` in the `actorType` property of the step’s input.
+
+***
+
+## 2. Define the Create API Route
+
+Next, you’ll use the workflow defined in the previous section in an API route that creates a manager.
+
+So, create the file `src/api/manager/route.ts` with the following content:
+
+```ts title="src/api/manager/route.ts" highlights={createRouteHighlights}
+import type {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import { MedusaError } from "@medusajs/framework/utils"
+import createManagerWorkflow from "../../workflows/create-manager"
+
+type RequestBody = {
+ first_name: string
+ last_name: string
+ email: string
+}
+
+export async function POST(
+ req: AuthenticatedMedusaRequest,
+ res: MedusaResponse
+) {
+ // If `actor_id` is present, the request carries
+ // authentication for an existing manager
+ if (req.auth_context.actor_id) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "Request already authenticated as a manager."
+ )
+ }
+
+ const { result } = await createManagerWorkflow(req.scope)
+ .run({
+ input: {
+ manager: req.body,
+ authIdentityId: req.auth_context.auth_identity_id,
+ },
+ })
+
+ res.status(200).json({ manager: result })
+}
+```
+
+Since the manager must be associated with an `AuthIdentity` record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by:
+
+1. Obtaining a token usng the [/auth route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
+2. Passing the token in the bearer header of the request to this route.
+
+In the API route, you create the manager using the workflow from the previous section and return it in the response.
+
+***
+
+## 3. Apply the `authenticate` Middleware
+
+The last step is to apply the `authenticate` middleware on the API routes that require a manager’s authentication.
+
+To do that, create the file `src/api/middlewares.ts` with the following content:
+
+```ts title="src/api/middlewares.ts" highlights={middlewareHighlights}
+import {
+ defineMiddlewares,
+ authenticate,
+} from "@medusajs/framework/http"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ matcher: "/manager",
+ method: "POST",
+ middlewares: [
+ authenticate("manager", ["session", "bearer"], {
+ allowUnregistered: true,
+ }),
+ ],
+ },
+ {
+ matcher: "/manager/me*",
+ middlewares: [
+ authenticate("manager", ["session", "bearer"]),
+ ],
+ },
+ ],
+})
+```
+
+This applies middlewares on two route patterns:
+
+1. The `authenticate` middleware is applied on the `/manager` API route for `POST` requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered.
+2. The `authenticate` middleware is applied on all routes starting with `/manager/me`, restricting these routes to authenticated managers only.
+
+### Retrieve Manager API Route
+
+For example, create the file `src/api/manager/me/route.ts` with the following content:
+
+```ts title="src/api/manager/me/route.ts"
+import {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+} from "@medusajs/framework/http"
+import ManagerModuleService from "../../../modules/manager/service"
+
+export async function GET(
+ req: AuthenticatedMedusaRequest,
+ res: MedusaResponse
+): Promise {
+ const managerModuleService: ManagerModuleService =
+ req.scope.resolve("managerModuleService")
+
+ const manager = await managerModuleService.retrieveManager(
+ req.auth_context.actor_id
+ )
+
+ res.json({ manager })
+}
+```
+
+This route is only accessible by authenticated managers. You access the manager’s ID using `req.auth_context.actor_id`.
+
+***
+
+## Test Custom Actor Type Authentication Flow
+
+To authenticate managers:
+
+1. Send a `POST` request to `/auth/manager/emailpass/register` to create an auth identity for the manager:
+
+```bash
+curl -X POST 'http://localhost:9000/auth/manager/emailpass/register' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "manager@gmail.com",
+ "password": "supersecret"
+}'
+```
+
+Copy the returned token to use it in the next request.
+
+2. Send a `POST` request to `/manager` to create a manager:
+
+```bash
+curl -X POST 'http://localhost:9000/manager' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data-raw '{
+ "first_name": "John",
+ "last_name": "Doe",
+ "email": "manager@gmail.com"
+}'
+```
+
+Replace `{token}` with the token returned in the previous step.
+
+3. Send a `POST` request to `/auth/manager/emailpass` again to retrieve an authenticated token for the manager:
+
+```bash
+curl -X POST 'http://localhost:9000/auth/manager/emailpass' \
+-H 'Content-Type: application/json' \
+--data-raw '{
+ "email": "manager@gmail.com",
+ "password": "supersecret"
+}'
+```
+
+4. You can now send authenticated requests as a manager. For example, send a `GET` request to `/manager/me` to retrieve the authenticated manager’s details:
+
+```bash
+curl 'http://localhost:9000/manager/me' \
+-H 'Authorization: Bearer {token}'
+```
+
+Whenever you want to log in as a manager, use the `/auth/manager/emailpass` API route, as explained in step 3.
+
+***
+
+## Delete User of Actor Type
+
+When you delete a user of the actor type, you must update its auth identity to remove the association to the user.
+
+For example, create the following workflow that deletes a manager and updates its auth identity, create the file `src/workflows/delete-manager.ts` with the following content:
+
+```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import {
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk"
-import { Modules } from "@medusajs/framework/utils"
+import ManagerModuleService from "../modules/manager/service"
-const createStoreStep = createStep(
- "create-store",
- async ({}, { container }) => {
- const storeModuleService = container.resolve(Modules.STORE)
+export type DeleteManagerWorkflow = {
+ id: string
+}
- const store = await storeModuleService.createStores({
- name: "My Store",
- supported_currencies: [{
- currency_code: "usd",
- is_default: true,
- }],
+const deleteManagerStep = createStep(
+ "delete-manager-step",
+ async (
+ { id }: DeleteManagerWorkflow,
+ { container }) => {
+ const managerModuleService: ManagerModuleService =
+ container.resolve("managerModuleService")
+
+ const manager = await managerModuleService.retrieve(id)
+
+ await managerModuleService.deleteManagers(id)
+
+ return new StepResponse(undefined, { manager })
+ },
+ async ({ manager }, { container }) => {
+ const managerModuleService: ManagerModuleService =
+ container.resolve("managerModuleService")
+
+ await managerModuleService.createManagers(manager)
+ }
+ )
+```
+
+You add a step that deletes the manager using the `deleteManagers` method of the module's main service. In the compensation function, you create the manager again.
+
+Next, in the same file, add the workflow that deletes a manager:
+
+```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-15" expandButtonLabel="Show Imports" highlights={deleteHighlights}
+// other imports
+import { MedusaError } from "@medusajs/framework/utils"
+import {
+ WorkflowData,
+ WorkflowResponse,
+ createWorkflow,
+ transform,
+} from "@medusajs/framework/workflows-sdk"
+import {
+ setAuthAppMetadataStep,
+ useQueryGraphStep,
+} from "@medusajs/medusa/core-flows"
+
+// ...
+
+export const deleteManagerWorkflow = createWorkflow(
+ "delete-manager",
+ (
+ input: WorkflowData
+ ): WorkflowResponse => {
+ deleteManagerStep(input)
+
+ const { data: authIdentities } = useQueryGraphStep({
+ entity: "auth_identity",
+ fields: ["id"],
+ filters: {
+ app_metadata: {
+ // the ID is of the format `{actor_type}_id`.
+ manager_id: input.id,
+ },
+ },
})
- return new StepResponse({ store }, store.id)
- },
- async (storeId, { container }) => {
- if(!storeId) {
- return
- }
- const storeModuleService = container.resolve(Modules.STORE)
-
- await storeModuleService.deleteStores([storeId])
- }
-)
+ const authIdentity = transform(
+ { authIdentities },
+ ({ authIdentities }) => {
+ const authIdentity = authIdentities[0]
-export const createStoreWorkflow = createWorkflow(
- "create-store",
- () => {
- const { store } = createStoreStep()
+ if (!authIdentity) {
+ throw new MedusaError(
+ MedusaError.Types.NOT_FOUND,
+ "Auth identity not found"
+ )
+ }
- return new WorkflowResponse({ store })
+ return authIdentity
+ }
+ )
+
+ setAuthAppMetadataStep({
+ authIdentityId: authIdentity.id,
+ actorType: "manager",
+ value: null,
+ })
+
+ return new WorkflowResponse(input.id)
}
)
```
-You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
+In the workflow, you:
-### API Route
+1. Use the `deleteManagerStep` defined earlier to delete the manager.
+2. Retrieve the auth identity of the manager using Query. To do that, you filter the `app_metadata` property of an auth identity, which holds the user's ID under `{actor_type_name}_id`. So, in this case, it's `manager_id`.
+3. Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it.
-```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import type {
- MedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { createStoreWorkflow } from "../../workflows/create-store"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createStoreWorkflow(req.scope)
- .run()
-
- res.send(result)
-}
-```
-
-### Subscriber
-
-```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- type SubscriberConfig,
- type SubscriberArgs,
-} from "@medusajs/framework"
-import { createStoreWorkflow } from "../workflows/create-store"
-
-export default async function handleUserCreated({
- event: { data },
- container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createStoreWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config: SubscriberConfig = {
- event: "user.created",
-}
-```
-
-### Scheduled Job
-
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createStoreWorkflow } from "../workflows/create-store"
-
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createStoreWorkflow(container)
- .run()
-
- console.log(result)
-}
-
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
-```
-
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
-
-***
+You can use this workflow when deleting a manager, such as in an API route.
-# Stock Location Module
+# Auth Module Options
-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 document, you'll learn about the options of the Auth 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.
+## providers
-Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md).
+The `providers` option is an array of auth module providers.
-## Stock Location Features
+When the Medusa application starts, these providers are registered and can be used to handle authentication.
-- [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 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.
-
-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.
+By default, the `emailpass` provider is registered to authenticate customers and admin users.
For example:
-```ts title="src/workflows/create-stock-location.ts" highlights={highlights}
-import {
- createWorkflow,
- WorkflowResponse,
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
+```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: [
+ {
+ resolve: "@medusajs/medusa/auth-emailpass",
+ id: "emailpass",
+ options: {
+ // provider options...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The `providers` option is an array of objects that accept the following properties:
+
+- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+***
+
+## Auth CORS
+
+The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration.
+
+By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`.
+
+Refer to [Medusa's configuration guide](https://docs.medusajs.com/references/medusa-config#authCors/index.html.md) to learn more about the `authCors` configuration.
+
+***
+
+## authMethodsPerActor Configuration
+
+The Medusa application's configuration accept an `authMethodsPerActor` configuration which restricts the allowed auth providers used with an actor type.
+
+Learn more about the `authMethodsPerActor` configuration in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers#configure-allowed-auth-providers-of-actor-types/index.html.md).
+
+
+# How to Handle Password Reset Token Event
+
+In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md).
+
+You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user.
+
+### Prerequisites
+
+- [A notification provider module, such as SendGrid](https://docs.medusajs.com/architectural-modules/notification/sendgrid/index.html.md)
+
+## 1. Create Subscriber
+
+The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password.
+
+Create the file `src/subscribers/handle-reset.ts` with the following content:
+
+```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports"
+import {
+ SubscriberArgs,
+ type SubscriberConfig,
+} from "@medusajs/medusa"
import { Modules } from "@medusajs/framework/utils"
-const createStockLocationStep = createStep(
- "create-stock-location",
- async ({}, { container }) => {
- const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
-
- const stockLocation = await stockLocationModuleService.createStockLocations({
- name: "Warehouse 1",
- })
-
- return new StepResponse({ stockLocation }, stockLocation.id)
- },
- async (stockLocationId, { container }) => {
- if (!stockLocationId) {
- return
- }
- const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
-
- await stockLocationModuleService.deleteStockLocations([stockLocationId])
- }
-)
-
-export const createStockLocationWorkflow = createWorkflow(
- "create-stock-location",
- () => {
- const { stockLocation } = createStockLocationStep()
-
- return new WorkflowResponse({ stockLocation })
- }
-)
-```
-
-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 { createStockLocationWorkflow } from "../../workflows/create-stock-location"
-
-export async function GET(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createStockLocationWorkflow(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 { createStockLocationWorkflow } from "../workflows/create-stock-location"
-
-export default async function handleUserCreated({
- event: { data },
+export default async function resetPasswordTokenHandler({
+ event: { data: {
+ entity_id: email,
+ token,
+ actor_type,
+ } },
container,
-}: SubscriberArgs<{ id: string }>) {
- const { result } = await createStockLocationWorkflow(container)
- .run()
+}: SubscriberArgs<{ entity_id: string, token: string, actor_type: string }>) {
+ const notificationModuleService = container.resolve(
+ Modules.NOTIFICATION
+ )
- console.log(result)
+ const urlPrefix = actor_type === "customer" ?
+ "https://storefront.com" :
+ "https://admin.com"
+
+ await notificationModuleService.createNotifications({
+ to: email,
+ channel: "email",
+ template: "reset-password-template",
+ data: {
+ // a URL to a frontend application
+ url: `${urlPrefix}/reset-password?token=${token}&email=${email}`,
+ },
+ })
}
export const config: SubscriberConfig = {
- event: "user.created",
+ event: "auth.password_reset",
}
```
-### Scheduled Job
+You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties:
-```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
-import { MedusaContainer } from "@medusajs/framework/types"
-import { createStockLocationWorkflow } from "../workflows/create-stock-location"
+- `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email.
+- `token`: The token to reset the user's password.
+- `actor_type`: The user's actor type. For example, if the user is a customer, the `actor_type` is `customer`. If it's an admin user, the `actor_type` is `user`.
-export default async function myCustomJob(
- container: MedusaContainer
-) {
- const { result } = await createStockLocationWorkflow(container)
- .run()
+This event's payload previously had an `actorType` field. It was renamed to `actor_type` after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7).
- console.log(result)
-}
+In the subscriber, you:
-export const config = {
- name: "run-once-a-day",
- schedule: `0 0 * * *`,
-}
+- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`.
+- Resolve the Notification Module and use its `createNotifications` method to send the notification.
+- You pass to the `createNotifications` method an object having the following properties:
+ - `to`: The identifier to send the notification to, which in this case is the email.
+ - `channel`: The channel to send the notification through, which in this case is email.
+ - `template`: The template ID in the third-party service.
+ - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password.
+
+***
+
+## 2. Test it Out: Generate Reset Password Token
+
+To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively.
+
+For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request:
+
+```bash
+curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "identifier": "admin-test@gmail.com"
+}'
```
-Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md).
+In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case.
+
+If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran:
+
+```plain
+info: Processing auth.password_reset which has 1 subscribers
+```
+
+The notification is sent to the user with the frontend URL to enter a new password.
***
+## Next Steps: Implementing Frontend
-# Cart Concepts
+In your frontend, you must have a page that accepts `token` and `email` query parameters.
-In this document, you’ll get an overview of the main concepts of a cart.
+The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md).
-## Shipping and Billing Addresses
+### Examples
-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).
+- [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.
+
+## API Key Types
+
+There are two types of API keys:
+
+- `publishable`: A public key used in client applications, such as a storefront.
+- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
+
+The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
***
-## Line Items
+## API Key Expiration
-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.
+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).
-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).
+The associated token is no longer usable or verifiable.
***
-## Shipping Methods
+## Token Verification
-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.
-
-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.
-
-### data Property
-
-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.
-
-The `data` property is an object used to store custom data relevant later for fulfillment.
+To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
# Links between API Key Module and Other Modules
@@ -17920,721 +19178,41 @@ await cartModuleService.setLineItemTaxLines(
```
-# Links between Currency Module and Other Modules
+# Cart Concepts
-This document showcases the module links defined between the Currency Module and other commerce modules.
+In this document, you’ll get an overview of the main concepts of a cart.
-## Summary
+## Shipping and Billing Addresses
-The Currency Module has the following links to other modules:
+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).
-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
+## Line Items
-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 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.
-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.
+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.
-### 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
-```
-
-
-# Fulfillment Concepts
-
-In this document, you’ll learn about some basic fulfillment concepts.
-
-## Fulfillment Set
-
-A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets.
-
-A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another.
-
-```ts
-const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets(
- [
- {
- name: "Shipping",
- type: "shipping",
- },
- {
- name: "Pick-up",
- type: "pick-up",
- },
- ]
-)
-```
+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).
***
-## Service Zone
+## Shipping Methods
-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 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.
-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.
+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.
-
+### data Property
-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.
+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.
-## Shipping Profile
-
-A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally.
-
-A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
-
-
-# API Key Concepts
-
-In this document, you’ll learn about the different types of API keys, their expiration and verification.
-
-## API Key Types
-
-There are two types of API keys:
-
-- `publishable`: A public key used in client applications, such as a storefront.
-- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
-
-The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md).
-
-***
-
-## API Key Expiration
-
-An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md).
-
-The associated token is no longer usable or verifiable.
-
-***
-
-## Token Verification
-
-To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens.
-
-
-# Fulfillment Module Provider
-
-In this document, you’ll learn what a fulfillment module provider is.
-
-## What’s a Fulfillment Module Provider?
-
-A fulfillment module provider handles fulfilling items, typically using a third-party integration.
-
-Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
-
-***
-
-## Configure Fulfillment Providers
-
-The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
-
-***
-
-## How to Create a Fulfillment Provider?
-
-Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
-
-
-# Item Fulfillment
-
-In this document, you’ll learn about the concepts of item fulfillment.
-
-## Fulfillment Data Model
-
-A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
-
-***
-
-## Fulfillment Processing by a Fulfillment Provider
-
-A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
-
-The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
-
-
-
-***
-
-## data Property
-
-The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
-
-For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
-
-***
-
-## Fulfillment Items
-
-A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
-
-The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
-
-
-
-***
-
-## Fulfillment Label
-
-Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
-
-***
-
-## Fulfillment Status
-
-The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
-
-- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
-- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
-- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
-
-
-# Fulfillment Module Options
-
-In this document, you'll learn about the options of the Fulfillment Module.
-
-## providers
-
-The `providers` option is an array of fulfillment module providers.
-
-When the Medusa application starts, these providers are registered and can be used to process fulfillments.
-
-For example:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/fulfillment",
- options: {
- providers: [
- {
- resolve: `@medusajs/medusa/fulfillment-manual`,
- id: "manual",
- options: {
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating either the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Shipping Option
-
-In this document, you’ll learn about shipping options and their rules.
-
-## What’s a Shipping Option?
-
-A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping.
-
-When the customer places their order, they choose a shipping option to be used to fulfill their items.
-
-A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md).
-
-***
-
-## Service Zone Restrictions
-
-A shipping option is restricted by a service zone, limiting the locations a shipping option be used in.
-
-For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada.
-
-
-
-Service zones can be more restrictive, such as restricting to certain cities or province codes.
-
-
-
-***
-
-## Shipping Option Rules
-
-You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
-
-These rules are represented by the [ShippingOptionRule data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionRule/index.html.md). Its properties define the custom rule:
-
-- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`.
-- `operator`: The operator used in the condition. For example:
- - To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values.
- - To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values.
- - Check out more operators in [this reference](https://docs.medusajs.com/references/fulfillment/types/fulfillment.RuleOperatorType/index.html.md).
-- `value`: One or more values.
-
-
-
-A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g.
-
-
-
-***
-
-## Shipping Profile and Types
-
-A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md).
-
-A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner.
-
-***
-
-## data Property
-
-When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process.
-
-The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment.
-
-
-# Links between Fulfillment Module and Other Modules
-
-This document showcases the module links defined between the Fulfillment Module and other commerce modules.
-
-## Summary
-
-The Fulfillment Module has the following links to other modules:
-
-- [`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).
-
-***
-
-## Order Module
-
-The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
-
-Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
-
-
-
-A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-To retrieve the return, pass `return.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: fulfillments } = await query.graph({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
-
-// fulfillments.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: fulfillments } = useQueryGraphStep({
- entity: "fulfillment",
- fields: [
- "order.*",
- ],
-})
-
-// fulfillments.order
-```
-
-### Manage with Link
-
-To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_id: "ful_123",
- },
-})
-```
-
-***
-
-## Pricing Module
-
-The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context.
-
-Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
-
-
-
-### Retrieve with Query
-
-To retrieve the price set of a shipping option with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: shippingOptions } = await query.graph({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
-
-// shippingOptions.price_set
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: shippingOptions } = useQueryGraphStep({
- entity: "shipping_option",
- fields: [
- "price_set.*",
- ],
-})
-
-// shippingOptions.price_set
-```
-
-### Manage with Link
-
-To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.FULFILLMENT]: {
- shipping_option_id: "so_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Product Module
-
-Medusa defines a link between the `ShippingProfile` data model and the `Product` data model of the Product Module. Each product must belong to a shipping profile.
-
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
-
-### Retrieve with Query
-
-To retrieve the products of a shipping profile with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: shippingProfiles } = await query.graph({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
-
-// shippingProfiles.products
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: shippingProfiles } = useQueryGraphStep({
- entity: "shipping_profile",
- fields: [
- "products.*",
- ],
-})
-
-// shippingProfiles.products
-```
-
-### Manage with Link
-
-To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-***
-
-## Stock Location Module
-
-The Stock Location Module provides features to manage stock locations in a store.
-
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location.
-
-
-
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
-
-
-
-### Retrieve with Query
-
-To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`:
-
-To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: fulfillmentSets } = await query.graph({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
-
-// fulfillmentSets.location
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: fulfillmentSets } = useQueryGraphStep({
- entity: "fulfillment_set",
- fields: [
- "location.*",
- ],
-})
-
-// fulfillmentSets.location
-```
-
-### Manage with Link
-
-To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.STOCK_LOCATION]: {
- stock_location_id: "sloc_123",
- },
- [Modules.FULFILLMENT]: {
- fulfillment_set_id: "fset_123",
- },
-})
-```
-
-
-# Inventory Concepts
-
-In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
-
-## InventoryItem
-
-An inventory item, represented by the [InventoryItem data model](https://docs.medusajs.com/references/inventory-next/models/InventoryItem/index.html.md), is a stock-kept item, such as a product, whose inventory can be managed.
-
-The `InventoryItem` data model mainly holds details related to the underlying stock item, but has relations to other data models that include its inventory details.
-
-
-
-### Inventory Shipping Requirement
-
-An inventory item has a `requires_shipping` field (enabled by default) that indicates whether the item requires shipping. For example, if you're selling a digital license that has limited stock quantity but doesn't require shipping.
-
-When a product variant is purchased in the Medusa application, this field is used to determine whether the item requires shipping. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md).
-
-***
-
-## InventoryLevel
-
-An inventory level, represented by the [InventoryLevel data model](https://docs.medusajs.com/references/inventory-next/models/InventoryLevel/index.html.md), holds the inventory and quantity details of an inventory item in a specific location.
-
-It has three quantity-related properties:
-
-- `stocked_quantity`: The available stock quantity of an item in the associated location.
-- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock.
-- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This property doesn't play into the `stocked_quantity` or when checking whether an item is in stock.
-
-### Associated Location
-
-The inventory level's location is determined by the `location_id` property. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module.
-
-***
-
-## ReservationItem
-
-A reservation item, represented by the [ReservationItem](https://docs.medusajs.com/references/inventory-next/models/ReservationItem/index.html.md) data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet.
-
-The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module.
+The `data` property is an object used to store custom data relevant later for fulfillment.
# Inventory Module in Medusa Flows
@@ -18698,341 +19276,6 @@ This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.
If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level.
-# Links between Inventory Module and Other Modules
-
-This document showcases the module links defined between the Inventory Module and other commerce modules.
-
-## Summary
-
-The Inventory Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-- [`ProductVariant` data model of Product Module \<> `InventoryItem` data model](#product-module).
-- [`InventoryLevel` data model \<> `StockLocation` data model of Stock Location Module](#stock-location-module). (Read-only).
-
-***
-
-## Product Module
-
-Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models.
-
-
-
-A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
-
-Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryItems } = await query.graph({
- entity: "inventory_item",
- fields: [
- "variants.*",
- ],
-})
-
-// inventoryItems.variants
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryItems } = useQueryGraphStep({
- entity: "inventory_item",
- fields: [
- "variants.*",
- ],
-})
-
-// inventoryItems.variants
-```
-
-### Manage with Link
-
-To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.INVENTORY]: {
- inventory_item_id: "iitem_123",
- },
-})
-```
-
-***
-
-## Stock Location Module
-
-Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model.
-
-### Retrieve with Query
-
-To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: inventoryLevels } = await query.graph({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: inventoryLevels } = useQueryGraphStep({
- entity: "inventory_level",
- fields: [
- "stock_locations.*",
- ],
-})
-
-// inventoryLevels.stock_locations
-```
-
-
-# Customer Accounts
-
-In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
-
-## `has_account` Property
-
-The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
-
-When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
-
-When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
-
-***
-
-## Email Uniqueness
-
-The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
-
-So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
-
-
-# Links between Customer Module and Other Modules
-
-This document showcases the module links defined between the Customer Module and other commerce modules.
-
-## Summary
-
-The Customer Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-- [`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).
-
-***
-
-## Payment Module
-
-Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
-
-This link is available starting from Medusa `v2.5.0`.
-
-### Retrieve with Query
-
-To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: customers } = await query.graph({
- entity: "customer",
- fields: [
- "account_holder.*",
- ],
-})
-
-// customers.account_holder
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: [
- "account_holder.*",
- ],
-})
-
-// customers.account_holder
-```
-
-### Manage with Link
-
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-***
-
-## Cart Module
-
-Medusa defines a read-only link between the `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 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: customers } = await query.graph({
- entity: "customer",
- fields: [
- "carts.*",
- ],
-})
-
-// customers.carts
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: [
- "carts.*",
- ],
-})
-
-// customers.carts
-```
-
-***
-
-## 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 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: customers } = await query.graph({
- entity: "customer",
- fields: [
- "orders.*",
- ],
-})
-
-// customers.orders
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: customers } = useQueryGraphStep({
- entity: "customer",
- fields: [
- "orders.*",
- ],
-})
-
-// customers.orders
-```
-
-
# Inventory Kits
In this guide, you'll learn how inventory kits can be used in the Medusa application to support use cases like multi-part products, bundled products, and shared inventory across products.
@@ -19416,2524 +19659,89 @@ 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).
-# Account Holders and Saved Payment Methods
+# Inventory Concepts
-In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
-Account holders are available starting from Medusa `v2.5.0`.
+## InventoryItem
-## What's an Account Holder?
+An inventory item, represented by the [InventoryItem data model](https://docs.medusajs.com/references/inventory-next/models/InventoryItem/index.html.md), is a stock-kept item, such as a product, whose inventory can be managed.
-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.
+The `InventoryItem` data model mainly holds details related to the underlying stock item, but has relations to other data models that include its inventory details.
-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.
+### Inventory Shipping Requirement
-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.
+An inventory item has a `requires_shipping` field (enabled by default) that indicates whether the item requires shipping. For example, if you're selling a digital license that has limited stock quantity but doesn't require shipping.
+
+When a product variant is purchased in the Medusa application, this field is used to determine whether the item requires shipping. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md).
***
-## Save Payment Methods
+## InventoryLevel
-If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+An inventory level, represented by the [InventoryLevel data model](https://docs.medusajs.com/references/inventory-next/models/InventoryLevel/index.html.md), holds the inventory and quantity details of an inventory item in a specific location.
-- `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.
+It has three quantity-related properties:
-Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
+- `stocked_quantity`: The available stock quantity of an item in the associated location.
+- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock.
+- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This property doesn't play into the `stocked_quantity` or when checking whether an item is in stock.
+
+### Associated Location
+
+The inventory level's location is determined by the `location_id` property. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module.
***
-## Account Holder in Medusa Payment Flows
+## ReservationItem
-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.
+A reservation item, represented by the [ReservationItem](https://docs.medusajs.com/references/inventory-next/models/ReservationItem/index.html.md) data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet.
-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.
+The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module.
-This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
+# Links between Inventory Module and Other Modules
-# Payment Module Options
-
-In this document, you'll learn about the options of the Payment Module.
-
-## All Module Options
-
-|Option|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
-|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
-|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
-
-***
-
-## providers Option
-
-The `providers` option is an array of payment module providers.
-
-When the Medusa application starts, these providers are registered and can be used to process payments.
-
-For example:
-
-```ts title="medusa-config.ts"
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/payment",
- options: {
- providers: [
- {
- resolve: "@medusajs/medusa/payment-stripe",
- id: "stripe",
- options: {
- // ...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-
-# Payment
-
-In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
-
-## What's a Payment?
-
-When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
-
-A payment carries many of the data and relations of a payment session:
-
-- It belongs to the same payment collection.
-- It’s associated with the same payment provider, which handles further payment processing.
-- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
-
-***
-
-## Capture Payments
-
-When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
-
-The payment can also be captured incrementally, each time a capture record is created for that amount.
-
-
-
-***
-
-## Refund Payments
-
-When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
-
-A payment can be refunded multiple times, and each time a refund record is created.
-
-
-
-
-# Payment Collection
-
-In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
-
-## What's a Payment Collection?
-
-A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
-
-Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
-
-- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
-- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
-- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
-
-***
-
-## Multiple Payments
-
-The payment collection supports multiple payment sessions and payments.
-
-You can use this to accept payments in increments or split payments across payment providers.
-
-
-
-***
-
-## Usage with the Cart Module
-
-The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
-
-During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
-
-It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
-
-
-
-
-# Accept Payment Flow
-
-In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
-
-It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
-
-For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
-
-## Flow Overview
-
-
-
-***
-
-## 1. Create a Payment Collection
-
-A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-await createPaymentCollectionForCartWorkflow(req.scope)
- .run({
- input: {
- cart_id: "cart_123",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentCollection =
- await paymentModuleService.createPaymentCollections({
- currency_code: "usd",
- amount: 5000,
- })
-```
-
-***
-
-## 2. Create Payment Sessions
-
-The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
-
-So, after creating the payment collection, create at least one payment session for a provider.
-
-For example:
-
-### Using Workflow
-
-```ts
-import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
- .run({
- input: {
- payment_collection_id: "paycol_123",
- provider_id: "stripe",
- },
- })
-```
-
-### Using Service
-
-```ts
-const paymentSession =
- await paymentModuleService.createPaymentSession(
- paymentCollection.id,
- {
- provider_id: "stripe",
- currency_code: "usd",
- amount: 5000,
- data: {
- // any necessary data for the
- // payment provider
- },
- }
- )
-```
-
-***
-
-## 3. Authorize Payment Session
-
-Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
-
-For example:
-
-### Using Step
-
-```ts
-import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-### Using Service
-
-```ts
-const payment = authorizePaymentSessionStep({
- id: "payses_123",
- context: {},
-})
-```
-
-When the payment authorization is successful, a payment is created and returned.
-
-### Handling Additional Action
-
-If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
-
-If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
-
-In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
-
-For example:
-
-```ts
-try {
- const payment =
- await paymentModuleService.authorizePaymentSession(
- paymentSession.id,
- {}
- )
-} catch (e) {
- // retrieve the payment session again
- const updatedPaymentSession = (
- await paymentModuleService.listPaymentSessions({
- id: [paymentSession.id],
- })
- )[0]
-
- if (updatedPaymentSession.status === "requires_more") {
- // TODO perform required action
- // TODO authorize payment again.
- }
-}
-```
-
-***
-
-## 4. Payment Flow Complete
-
-The payment flow is complete once the payment session is authorized and the payment is created.
-
-You can then:
-
-- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
-- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
-
-Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
-
-
-# Payment Module Provider
-
-In this document, you’ll learn what a payment module provider is.
-
-## What's a Payment Module Provider?
-
-A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
-
-To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
-
-After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
-
-### List of Payment Module Providers
-
-- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
-
-***
-
-## System Payment Provider
-
-The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
-
-It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
-
-***
-
-## How are Payment Providers Created?
-
-A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
-
-Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
-
-***
-
-## Configure Payment Providers
-
-The Payment Module accepts a `providers` option that allows you to register providers in your application.
-
-Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
-
-***
-
-## PaymentProvider Data Model
-
-When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
-
-This data model is used to reference a payment provider and determine whether it’s installed in the application.
-
-
-# Webhook Events
-
-In this document, you’ll learn how the Payment Module supports listening to webhook events.
-
-## What's a Webhook Event?
-
-A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
-
-This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
-
-***
-
-## getWebhookActionAndData Method
-
-The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
-
-Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
-
-- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
-- `[provider]` is the ID of the provider. For example, `stripe`.
-
-For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
-
-Use that webhook listener in your third-party payment provider's configurations.
-
-
-
-If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
-
-If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
-
-### Actions After Webhook Payment Processing
-
-After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
-
-
-# Payment Session
-
-In this document, you’ll learn what a payment session is.
-
-## What's a Payment Session?
-
-A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
-
-A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
-
-
-
-***
-
-## data Property
-
-Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
-
-For example, the customer's ID in Stripe is stored in the `data` property.
-
-***
-
-## Payment Session Status
-
-The `status` property of a payment session indicates its current status. Its value can be:
-
-- `pending`: The payment session is awaiting authorization.
-- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
-- `authorized`: The payment session is authorized.
-- `error`: An error occurred while authorizing the payment.
-- `canceled`: The authorization of the payment session has been canceled.
-
-
-# Links between Payment Module and Other Modules
-
-This document showcases the module links defined between the Payment Module and other commerce modules.
+This document showcases the module links defined between the Inventory Module and other commerce modules.
## 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).
-
-***
-
-## Cart Module
-
-The Cart Module provides cart-related features, but not payment processing.
-
-Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
-
-Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
-
-### Retrieve with Query
-
-To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "cart.*",
- ],
-})
-
-// paymentCollections.cart
-```
-
-### Manage with Link
-
-To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Customer Module
-
-Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
-
-This link is available starting from Medusa `v2.5.0`.
-
-### Retrieve with Query
-
-To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: accountHolders } = await query.graph({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: accountHolders } = useQueryGraphStep({
- entity: "account_holder",
- fields: [
- "customer.*",
- ],
-})
-
-// accountHolders.customer
-```
-
-### Manage with Link
-
-To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CUSTOMER]: {
- customer_id: "cus_123",
- },
- [Modules.PAYMENT]: {
- account_holder_id: "acchld_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
-
-So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentCollections } = await query.graph({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentCollections } = useQueryGraphStep({
- entity: "payment_collection",
- fields: [
- "order.*",
- ],
-})
-
-// paymentCollections.order
-```
-
-### Manage with Link
-
-To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PAYMENT]: {
- payment_collection_id: "paycol_123",
- },
-})
-```
-
-***
-
-## Region Module
-
-You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
-
-
-
-This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
-
-### Retrieve with Query
-
-To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: paymentProviders } = await query.graph({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: paymentProviders } = useQueryGraphStep({
- entity: "payment_provider",
- fields: [
- "regions.*",
- ],
-})
-
-// paymentProviders.regions
-```
-
-### Manage with Link
-
-To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.REGION]: {
- region_id: "reg_123",
- },
- [Modules.PAYMENT]: {
- payment_provider_id: "pp_stripe_stripe",
- },
-})
-```
-
-
-# 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.
-
-
-# How to Create an Actor Type
-
-In this document, learn how to create an actor type and authenticate its associated data model.
-
-## 0. Create Module with Data Model
-
-Before creating an actor type, you must have a module with a data model representing the actor type.
-
-Learn how to create a module in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md).
-
-The rest of this guide uses this `Manager` data model as an example:
-
-```ts title="src/modules/manager/models/manager.ts"
-import { model } from "@medusajs/framework/utils"
-
-const Manager = model.define("manager", {
- id: model.id().primaryKey(),
- firstName: model.text(),
- lastName: model.text(),
- email: model.text(),
-})
-
-export default Manager
-```
-
-***
-
-## 1. Create Workflow
-
-Start by creating a workflow that does two things:
-
-- Creates a record of the `Manager` data model.
-- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type.
-
-For example, create the file `src/workflows/create-manager.ts`. with the following content:
-
-```ts title="src/workflows/create-manager.ts" highlights={workflowHighlights}
-import {
- createWorkflow,
- createStep,
- StepResponse,
- WorkflowResponse,
-} from "@medusajs/framework/workflows-sdk"
-import {
- setAuthAppMetadataStep,
-} from "@medusajs/medusa/core-flows"
-import ManagerModuleService from "../modules/manager/service"
-
-type CreateManagerWorkflowInput = {
- manager: {
- first_name: string
- last_name: string
- email: string
- }
- authIdentityId: string
-}
-
-const createManagerStep = createStep(
- "create-manager-step",
- async ({
- manager: managerData,
- }: Pick,
- { container }) => {
- const managerModuleService: ManagerModuleService =
- container.resolve("managerModuleService")
-
- const manager = await managerModuleService.createManager(
- managerData
- )
-
- return new StepResponse(manager)
- }
-)
-
-const createManagerWorkflow = createWorkflow(
- "create-manager",
- function (input: CreateManagerWorkflowInput) {
- const manager = createManagerStep({
- manager: input.manager,
- })
-
- setAuthAppMetadataStep({
- authIdentityId: input.authIdentityId,
- actorType: "manager",
- value: manager.id,
- })
-
- return new WorkflowResponse(manager)
- }
-)
-
-export default createManagerWorkflow
-```
-
-This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved.
-
-The workflow has two steps:
-
-1. Create the manager using the `createManagerStep`.
-2. Set the `app_metadata` property of the associated auth identity using the `setAuthAppMetadataStep` from Medusa's core workflows. You specify the actor type `manager` in the `actorType` property of the step’s input.
-
-***
-
-## 2. Define the Create API Route
-
-Next, you’ll use the workflow defined in the previous section in an API route that creates a manager.
-
-So, create the file `src/api/manager/route.ts` with the following content:
-
-```ts title="src/api/manager/route.ts" highlights={createRouteHighlights}
-import type {
- AuthenticatedMedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import { MedusaError } from "@medusajs/framework/utils"
-import createManagerWorkflow from "../../workflows/create-manager"
-
-type RequestBody = {
- first_name: string
- last_name: string
- email: string
-}
-
-export async function POST(
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-) {
- // If `actor_id` is present, the request carries
- // authentication for an existing manager
- if (req.auth_context.actor_id) {
- throw new MedusaError(
- MedusaError.Types.INVALID_DATA,
- "Request already authenticated as a manager."
- )
- }
-
- const { result } = await createManagerWorkflow(req.scope)
- .run({
- input: {
- manager: req.body,
- authIdentityId: req.auth_context.auth_identity_id,
- },
- })
-
- res.status(200).json({ manager: result })
-}
-```
-
-Since the manager must be associated with an `AuthIdentity` record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by:
-
-1. Obtaining a token usng the [/auth route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
-2. Passing the token in the bearer header of the request to this route.
-
-In the API route, you create the manager using the workflow from the previous section and return it in the response.
-
-***
-
-## 3. Apply the `authenticate` Middleware
-
-The last step is to apply the `authenticate` middleware on the API routes that require a manager’s authentication.
-
-To do that, create the file `src/api/middlewares.ts` with the following content:
-
-```ts title="src/api/middlewares.ts" highlights={middlewareHighlights}
-import {
- defineMiddlewares,
- authenticate,
-} from "@medusajs/framework/http"
-
-export default defineMiddlewares({
- routes: [
- {
- matcher: "/manager",
- method: "POST",
- middlewares: [
- authenticate("manager", ["session", "bearer"], {
- allowUnregistered: true,
- }),
- ],
- },
- {
- matcher: "/manager/me*",
- middlewares: [
- authenticate("manager", ["session", "bearer"]),
- ],
- },
- ],
-})
-```
-
-This applies middlewares on two route patterns:
-
-1. The `authenticate` middleware is applied on the `/manager` API route for `POST` requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered.
-2. The `authenticate` middleware is applied on all routes starting with `/manager/me`, restricting these routes to authenticated managers only.
-
-### Retrieve Manager API Route
-
-For example, create the file `src/api/manager/me/route.ts` with the following content:
-
-```ts title="src/api/manager/me/route.ts"
-import {
- AuthenticatedMedusaRequest,
- MedusaResponse,
-} from "@medusajs/framework/http"
-import ManagerModuleService from "../../../modules/manager/service"
-
-export async function GET(
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-): Promise {
- const managerModuleService: ManagerModuleService =
- req.scope.resolve("managerModuleService")
-
- const manager = await managerModuleService.retrieveManager(
- req.auth_context.actor_id
- )
-
- res.json({ manager })
-}
-```
-
-This route is only accessible by authenticated managers. You access the manager’s ID using `req.auth_context.actor_id`.
-
-***
-
-## Test Custom Actor Type Authentication Flow
-
-To authenticate managers:
-
-1. Send a `POST` request to `/auth/manager/emailpass/register` to create an auth identity for the manager:
-
-```bash
-curl -X POST 'http://localhost:9000/auth/manager/emailpass/register' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "manager@gmail.com",
- "password": "supersecret"
-}'
-```
-
-Copy the returned token to use it in the next request.
-
-2. Send a `POST` request to `/manager` to create a manager:
-
-```bash
-curl -X POST 'http://localhost:9000/manager' \
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {token}' \
---data-raw '{
- "first_name": "John",
- "last_name": "Doe",
- "email": "manager@gmail.com"
-}'
-```
-
-Replace `{token}` with the token returned in the previous step.
-
-3. Send a `POST` request to `/auth/manager/emailpass` again to retrieve an authenticated token for the manager:
-
-```bash
-curl -X POST 'http://localhost:9000/auth/manager/emailpass' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "manager@gmail.com",
- "password": "supersecret"
-}'
-```
-
-4. You can now send authenticated requests as a manager. For example, send a `GET` request to `/manager/me` to retrieve the authenticated manager’s details:
-
-```bash
-curl 'http://localhost:9000/manager/me' \
--H 'Authorization: Bearer {token}'
-```
-
-Whenever you want to log in as a manager, use the `/auth/manager/emailpass` API route, as explained in step 3.
-
-***
-
-## Delete User of Actor Type
-
-When you delete a user of the actor type, you must update its auth identity to remove the association to the user.
-
-For example, create the following workflow that deletes a manager and updates its auth identity, create the file `src/workflows/delete-manager.ts` with the following content:
-
-```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
-import {
- createStep,
- StepResponse,
-} from "@medusajs/framework/workflows-sdk"
-import ManagerModuleService from "../modules/manager/service"
-
-export type DeleteManagerWorkflow = {
- id: string
-}
-
-const deleteManagerStep = createStep(
- "delete-manager-step",
- async (
- { id }: DeleteManagerWorkflow,
- { container }) => {
- const managerModuleService: ManagerModuleService =
- container.resolve("managerModuleService")
-
- const manager = await managerModuleService.retrieve(id)
-
- await managerModuleService.deleteManagers(id)
-
- return new StepResponse(undefined, { manager })
- },
- async ({ manager }, { container }) => {
- const managerModuleService: ManagerModuleService =
- container.resolve("managerModuleService")
-
- await managerModuleService.createManagers(manager)
- }
- )
-```
-
-You add a step that deletes the manager using the `deleteManagers` method of the module's main service. In the compensation function, you create the manager again.
-
-Next, in the same file, add the workflow that deletes a manager:
-
-```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-15" expandButtonLabel="Show Imports" highlights={deleteHighlights}
-// other imports
-import { MedusaError } from "@medusajs/framework/utils"
-import {
- WorkflowData,
- WorkflowResponse,
- createWorkflow,
- transform,
-} from "@medusajs/framework/workflows-sdk"
-import {
- setAuthAppMetadataStep,
- useQueryGraphStep,
-} from "@medusajs/medusa/core-flows"
-
-// ...
-
-export const deleteManagerWorkflow = createWorkflow(
- "delete-manager",
- (
- input: WorkflowData
- ): WorkflowResponse => {
- deleteManagerStep(input)
-
- const { data: authIdentities } = useQueryGraphStep({
- entity: "auth_identity",
- fields: ["id"],
- filters: {
- app_metadata: {
- // the ID is of the format `{actor_type}_id`.
- manager_id: input.id,
- },
- },
- })
-
- const authIdentity = transform(
- { authIdentities },
- ({ authIdentities }) => {
- const authIdentity = authIdentities[0]
-
- if (!authIdentity) {
- throw new MedusaError(
- MedusaError.Types.NOT_FOUND,
- "Auth identity not found"
- )
- }
-
- return authIdentity
- }
- )
-
- setAuthAppMetadataStep({
- authIdentityId: authIdentity.id,
- actorType: "manager",
- value: null,
- })
-
- return new WorkflowResponse(input.id)
- }
-)
-```
-
-In the workflow, you:
-
-1. Use the `deleteManagerStep` defined earlier to delete the manager.
-2. Retrieve the auth identity of the manager using Query. To do that, you filter the `app_metadata` property of an auth identity, which holds the user's ID under `{actor_type_name}_id`. So, in this case, it's `manager_id`.
-3. Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it.
-
-You can use this workflow when deleting a manager, such as in an API route.
-
-
-# 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).
-
-
-# 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.
-
-## providers
-
-The `providers` option is an array of auth module providers.
-
-When the Medusa application starts, these providers are registered and can be used to handle authentication.
-
-By default, the `emailpass` provider is registered to authenticate customers and admin users.
-
-For example:
-
-```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: [
- {
- resolve: "@medusajs/medusa/auth-emailpass",
- id: "emailpass",
- options: {
- // provider options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-The `providers` option is an array of objects that accept the following properties:
-
-- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
-- `id`: A string indicating the provider's unique name or ID.
-- `options`: An optional object of the module provider's options.
-
-***
-
-## Auth CORS
-
-The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration.
-
-By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`.
-
-Refer to [Medusa's configuration guide](https://docs.medusajs.com/references/medusa-config#authCors/index.html.md) to learn more about the `authCors` configuration.
-
-***
-
-## authMethodsPerActor Configuration
-
-The Medusa application's configuration accept an `authMethodsPerActor` configuration which restricts the allowed auth providers used with an actor type.
-
-Learn more about the `authMethodsPerActor` configuration in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers#configure-allowed-auth-providers-of-actor-types/index.html.md).
-
-
-# How to Handle Password Reset Token Event
-
-In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md).
-
-You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user.
-
-### Prerequisites
-
-- [A notification provider module, such as SendGrid](https://docs.medusajs.com/architectural-modules/notification/sendgrid/index.html.md)
-
-## 1. Create Subscriber
-
-The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password.
-
-Create the file `src/subscribers/handle-reset.ts` with the following content:
-
-```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports"
-import {
- SubscriberArgs,
- type SubscriberConfig,
-} from "@medusajs/medusa"
-import { Modules } from "@medusajs/framework/utils"
-
-export default async function resetPasswordTokenHandler({
- event: { data: {
- entity_id: email,
- token,
- actor_type,
- } },
- container,
-}: SubscriberArgs<{ entity_id: string, token: string, actor_type: string }>) {
- const notificationModuleService = container.resolve(
- Modules.NOTIFICATION
- )
-
- const urlPrefix = actor_type === "customer" ?
- "https://storefront.com" :
- "https://admin.com"
-
- await notificationModuleService.createNotifications({
- to: email,
- channel: "email",
- template: "reset-password-template",
- data: {
- // a URL to a frontend application
- url: `${urlPrefix}/reset-password?token=${token}&email=${email}`,
- },
- })
-}
-
-export const config: SubscriberConfig = {
- event: "auth.password_reset",
-}
-```
-
-You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties:
-
-- `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email.
-- `token`: The token to reset the user's password.
-- `actor_type`: The user's actor type. For example, if the user is a customer, the `actor_type` is `customer`. If it's an admin user, the `actor_type` is `user`.
-
-This event's payload previously had an `actorType` field. It was renamed to `actor_type` after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7).
-
-In the subscriber, you:
-
-- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`.
-- Resolve the Notification Module and use its `createNotifications` method to send the notification.
-- You pass to the `createNotifications` method an object having the following properties:
- - `to`: The identifier to send the notification to, which in this case is the email.
- - `channel`: The channel to send the notification through, which in this case is email.
- - `template`: The template ID in the third-party service.
- - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password.
-
-***
-
-## 2. Test it Out: Generate Reset Password Token
-
-To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively.
-
-For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request:
-
-```bash
-curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \
---header 'Content-Type: application/json' \
---data-raw '{
- "identifier": "admin-test@gmail.com"
-}'
-```
-
-In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case.
-
-If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran:
-
-```plain
-info: Processing auth.password_reset which has 1 subscribers
-```
-
-The notification is sent to the user with the frontend URL to enter a new password.
-
-***
-
-## Next Steps: Implementing Frontend
-
-In your frontend, you must have a page that accepts `token` and `email` query parameters.
-
-The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md).
-
-### Examples
-
-- [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
-
-
-# How to Use Authentication Routes
-
-In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password.
-
-These routes are added by Medusa's HTTP layer, not the Auth Module.
-
-## Types of Authentication Flows
-
-### 1. Basic Authentication Flow
-
-This authentication flow doesn't require validation with third-party services.
-
-[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md).
-
-The steps are:
-
-
-
-1. Register the user with the [Register Route](#register-route).
-2. Use the authentication token to create the user with their respective API route.
- - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
- - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
-3. Authenticate the user with the [Auth Route](#login-route).
-
-After registration, you only use the [Auth Route](#login-route) for subsequent authentication.
-
-To handle errors related to existing identities, refer to [this section](#handling-existing-identities).
-
-### 2. Third-Party Service Authenticate Flow
-
-This authentication flow authenticates the user with a third-party service, such as Google.
-
-[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
-
-It requires the following steps:
-
-
-
-1. Authenticate the user with the [Auth Route](#login-route).
-2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location.
-3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication.
-4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters.
-5. If the callback validation is successful, the frontend receives the authentication token.
-6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt).
- - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests.
- - If not, follow the rest of the steps.
-7. The frontend uses the authentication token to create the user with their respective API route.
- - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
- - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept)
-8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated.
-
-***
-
-## Register Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com"
- // ...
-}'
-```
-
-This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead.
-
-For example, if you're registering a customer, you:
-
-1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token.
-2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication).
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-### Request Body Parameters
-
-This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
-
-For example, the EmailPass provider requires an `email` and `password` fields in the request body.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-### Handling Existing Identities
-
-An auth identity with the same email may already exist in Medusa. This can happen if:
-
-- Another actor type is using that email. For example, an admin user is trying to register as a customer.
-- The same email belongs to a record of the same actor type. For example, another customer has the same email.
-
-In these scenarios, the Register Route will return an error instead of a token:
-
-```json
-{
- "type": "unauthorized",
- "message": "Identity with email already exists"
-}
-```
-
-To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer.
-
-Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error:
-
-```json
-{
- "type": "unauthorized",
- "message": "Invalid email or password"
-}
-```
-
-You can show that error message to the customer.
-
-***
-
-## Login Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com"
- // ...
-}'
-```
-
-For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`.
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-### Request Body Parameters
-
-This route accepts in the request body the data that the specified authentication provider requires to handle authentication.
-
-For example, the EmailPass provider requires an `email` and `password` fields in the request body.
-
-#### Overriding Callback URL
-
-For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations.
-
-This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-If the authentication requires more action with a third-party service, you'll receive a `location` property:
-
-```json
-{
- "location": "https://..."
-}
-```
-
-Redirect to that URL in the frontend to continue the authentication process with the third-party service.
-
-[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md).
-
-***
-
-## Validate Callback Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456
-```
-
-Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow.
-
-### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `google`.
-
-### Query Parameters
-
-This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters.
-
-### Response Fields
-
-If the authentication is successful, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt):
-
-- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests.
-- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers).
-
-***
-
-## Refresh Token Route
-
-The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information.
-
-It requires the user's JWT token that they received from the authentication or callback routes.
-
-```bash
-curl -X POST http://localhost:9000/auth/token/refresh \
--H 'Authorization: Bearer {token}'
-```
-
-### Response Fields
-
-If the token was refreshed successfully, you'll receive a `token` field in the response body object:
-
-```json
-{
- "token": "..."
-}
-```
-
-Use that token in the header of subsequent requests to send authenticated requests.
-
-***
-
-## Reset Password Routes
-
-To reset a user's password:
-
-1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route).
- - The API route emits the `auth.password_reset` event, passing the token in the payload.
- - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user.
-2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password.
- - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route.
-
-[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md)
-
-### Generate Reset Password Token Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password
--H 'Content-Type: application/json' \
---data-raw '{
- "identifier": "Whitney_Schultz@gmail.com"
-}'
-```
-
-This API route is useful for providers like `emailpass` that store a user's password and use it for authentication.
-
-#### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-#### Request Body Parameters
-
-This route accepts in the request body an object having the following property:
-
-- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email.
-
-#### Response Fields
-
-If the authentication is successful, the request returns a `201` response code.
-
-### Reset Password Route
-
-The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password.
-
-```bash
-curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update?token=123
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "Whitney_Schultz@gmail.com",
- "password": "supersecret"
-}'
-```
-
-This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in.
-
-#### Path Parameters
-
-Its path parameters are:
-
-- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`.
-- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`.
-
-#### Query Parameters
-
-The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route).
-
-### Request Body Parameters
-
-This route accepts in the request body an object that has the data necessary for the provider to update the user's password.
-
-For the `emailpass` provider, you must pass the following properties:
-
-- `email`: The user's email.
-- `password`: The new password.
-
-### Response Fields
-
-If the authentication is successful, the request returns an object with a `success` property set to `true`:
-
-```json
-{
- "success": "true"
-}
-```
-
-
-# Configure Selling Products
-
-In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
-
-The concepts in this guide are applicable starting from Medusa v2.5.1.
-
-## Scenario
-
-Businesses can have different selling requirements:
-
-1. They may sell physical or digital items.
-2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
-3. They may sell items whose inventory is managed by an external system, such as an ERP.
-
-Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
-
-***
-
-## Configuring Shipping Requirements
-
-The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
-
-When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
-
-If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
-
-### Overriding Shipping Requirements for Variants
-
-A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
-
-Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
-
-When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
-
-1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
-2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
-
-***
-
-## Use Case Examples
-
-By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
-
-|Use Case|Configurations|Example|
-|---|---|---|---|---|
-|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
-|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
-|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
-|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
-
-
-# Product Variant Inventory
-
-# Product Variant Inventory
-
-In this guide, you'll learn about the inventory management features related to product variants.
-
-## Configure Inventory Management of Product Variants
-
-A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
-
-The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
-
-When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
-
-***
-
-## How the Medusa Application Manages Inventory
-
-When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
-
-
-
-The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
-
-
-
-Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
-
-The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
-
-
-
-Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
-
-### Product Inventory in Storefronts
-
-When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
-
-The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
-
-For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
-
-
-
-***
-
-## Variant Back Orders
-
-Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
-
-You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
-
-***
-
-## Additional Resources
-
-The following guides provide more details on inventory management in the Medusa application:
-
-- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
-- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
-- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
-- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
-
-
-# Promotion Actions
-
-In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
-
-## computeActions Method
-
-The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
-
-Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
-
-***
-
-## Action Types
-
-### `addItemAdjustment` Action
-
-The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
-
-This action has the following format:
-
-```ts
-export interface AddItemAdjustmentAction {
- action: "addItemAdjustment"
- item_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `removeItemAdjustment` Action
-
-The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
-
-The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveItemAdjustmentAction {
- action: "removeItemAdjustment"
- adjustment_id: string
- description?: string
- code: string
-}
-```
-
-This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
-
-### `addShippingMethodAdjustment` Action
-
-The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
-
-This action has the following format:
-
-```ts
-export interface AddShippingMethodAdjustment {
- action: "addShippingMethodAdjustment"
- shipping_method_id: string
- amount: number
- code: string
- description?: string
-}
-```
-
-This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `removeShippingMethodAdjustment` Action
-
-The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
-
-The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
-
-This action has the following format:
-
-```ts
-export interface RemoveShippingMethodAdjustment {
- action: "removeShippingMethodAdjustment"
- adjustment_id: string
- code: string
-}
-```
-
-When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
-
-### `campaignBudgetExceeded` Action
-
-When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
-
-This action has the following format:
-
-```ts
-export interface CampaignBudgetExceededAction {
- action: "campaignBudgetExceeded"
- code: string
-}
-```
-
-Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
-
-
-# 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.
-
-
-# Promotion Concepts
-
-In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module.
-
-## What is a Promotion?
-
-A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders.
-
-A promotion has two types:
-
-- `standard`: A standard promotion with rules.
-- `buyget`: “A buy X get Y” promotion with rules.
-
-|\`standard\`|\`buyget\`|
-|---|---|
-|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.|
-|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.|
-|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.|
-
-The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes.
-
-***
-
-## PromotionRule
-
-A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md).
-
-For example, you can create a promotion that only customers of the `VIP` customer group can use.
-
-
-
-A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied.
-
-For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
-
-When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself.
-
-For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
-
-***
-
-## Flexible Rules
-
-The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`).
-
-For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
-
-- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
-- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`.
-
-
-
-In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion.
-
-
-# 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:
+The Inventory Module has the following links to other modules:
Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-- [`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).
+- [`ProductVariant` data model of Product Module \<> `InventoryItem` data model](#product-module).
+- [`InventoryLevel` data model \<> `StockLocation` data model of Stock Location Module](#stock-location-module). (Read-only).
***
-## Cart Module
+## Product Module
-Medusa defines read-only links between:
+Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models.
-- 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
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "line_items.*",
- ],
-})
-
-// variants.line_items
-```
-
-***
-
-## Fulfillment Module
-
-Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile.
-
-This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
-
-### Retrieve with Query
-
-To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "shipping_profile.*",
- ],
-})
-
-// products.shipping_profile
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: [
- "shipping_profile.*",
- ],
-})
-
-// products.shipping_profile
-```
-
-### Manage with Link
-
-To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.FULFILLMENT]: {
- shipping_profile_id: "sp_123",
- },
-})
-```
-
-***
-
-## Inventory Module
-
-The Inventory Module provides inventory-management features for any stock-kept item.
-
-Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details.
-
-
-
-When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
+A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
### Retrieve with Query
-To retrieve the 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 product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`:
### query.graph
```ts
-const { data: variants } = await query.graph({
- entity: "variant",
+const { data: inventoryItems } = await query.graph({
+ entity: "inventory_item",
fields: [
- "inventory_items.*",
+ "variants.*",
],
})
-// variants.inventory_items
+// inventoryItems.variants
```
### useQueryGraphStep
@@ -21943,19 +19751,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
+const { data: inventoryItems } = useQueryGraphStep({
+ entity: "inventory_item",
fields: [
- "inventory_items.*",
+ "variants.*",
],
})
-// variants.inventory_items
+// inventoryItems.variants
```
### 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 variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
### link.create
@@ -21994,30 +19802,25 @@ createRemoteLinkStep({
***
-## Order Module
+## Stock Location Module
-Medusa defines read-only links between:
-
-- 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.
+Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model.
### Retrieve with Query
-To retrieve the 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`.
+To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`:
### query.graph
```ts
-const { data: variants } = await query.graph({
- entity: "variant",
+const { data: inventoryLevels } = await query.graph({
+ entity: "inventory_level",
fields: [
- "order_items.*",
+ "stock_locations.*",
],
})
-// variants.order_items
+// inventoryLevels.stock_locations
```
### useQueryGraphStep
@@ -22027,429 +19830,250 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
+const { data: inventoryLevels } = useQueryGraphStep({
+ entity: "inventory_level",
fields: [
- "order_items.*",
+ "stock_locations.*",
],
})
-// variants.order_items
+// inventoryLevels.stock_locations
+```
+
+
+# Fulfillment Concepts
+
+In this document, you’ll learn about some basic fulfillment concepts.
+
+## Fulfillment Set
+
+A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets.
+
+A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another.
+
+```ts
+const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets(
+ [
+ {
+ name: "Shipping",
+ type: "shipping",
+ },
+ {
+ name: "Pick-up",
+ type: "pick-up",
+ },
+ ]
+)
+```
+
+***
+
+## Service Zone
+
+A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations.
+
+A service zone is represented by the [ServiceZone data model](https://docs.medusajs.com/references/fulfillment/models/ServiceZone/index.html.md). It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location.
+
+
+
+A service zone can have multiple geographical zones, each represented by the [GeoZone data model](https://docs.medusajs.com/references/fulfillment/models/GeoZone/index.html.md). It holds location-related details to narrow down supported areas, such as country, city, or province code.
+
+***
+
+## Shipping Profile
+
+A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally.
+
+A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
+
+
+# Fulfillment Module Provider
+
+In this document, you’ll learn what a fulfillment module provider is.
+
+## What’s a Fulfillment Module Provider?
+
+A fulfillment module provider handles fulfilling items, typically using a third-party integration.
+
+Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md).
+
+***
+
+## Configure Fulfillment Providers
+
+The Fulfillment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md).
+
+***
+
+## How to Create a Fulfillment Provider?
+
+Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider.
+
+
+# Item Fulfillment
+
+In this document, you’ll learn about the concepts of item fulfillment.
+
+## Fulfillment Data Model
+
+A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md).
+
+***
+
+## Fulfillment Processing by a Fulfillment Provider
+
+A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
+
+The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
+
+
+
+***
+
+## data Property
+
+The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
+
+For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
+
+***
+
+## Fulfillment Items
+
+A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
+
+The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
+
+
+
+***
+
+## Fulfillment Label
+
+Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
+
+***
+
+## Fulfillment Status
+
+The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
+
+- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
+- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
+- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
+
+
+# Links between Fulfillment Module and Other Modules
+
+This document showcases the module links defined between the Fulfillment Module and other commerce modules.
+
+## Summary
+
+The Fulfillment Module has the following links to other modules:
+
+- [`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).
+
+***
+
+## Order Module
+
+The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities.
+
+Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
+
+
+
+A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+To retrieve the return, pass `return.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: fulfillments } = await query.graph({
+ entity: "fulfillment",
+ fields: [
+ "order.*",
+ ],
+})
+
+// fulfillments.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: fulfillments } = useQueryGraphStep({
+ entity: "fulfillment",
+ fields: [
+ "order.*",
+ ],
+})
+
+// fulfillments.order
+```
+
+### Manage with Link
+
+To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.FULFILLMENT]: {
+ fulfillment_id: "ful_123",
+ },
+})
```
***
## Pricing Module
-The Product Module doesn't provide pricing-related features.
-
-Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set.
-
-
-
-So, to add prices for a product variant, create a price set and add the prices to it.
-
-### Retrieve with Query
-
-To retrieve the price set of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: variants } = await query.graph({
- entity: "variant",
- fields: [
- "price_set.*",
- ],
-})
-
-// variants.price_set
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: variants } = useQueryGraphStep({
- entity: "variant",
- fields: [
- "price_set.*",
- ],
-})
-
-// variants.price_set
-```
-
-### Manage with Link
-
-To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- variant_id: "variant_123",
- },
- [Modules.PRICING]: {
- price_set_id: "pset_123",
- },
-})
-```
-
-***
-
-## Sales Channel Module
-
-The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
-
-Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
-
-
-
-### Retrieve with Query
-
-To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: products } = await query.graph({
- entity: "product",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// products.sales_channels
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: products } = useQueryGraphStep({
- entity: "product",
- fields: [
- "sales_channels.*",
- ],
-})
-
-// products.sales_channels
-```
-
-### Manage with Link
-
-To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.PRODUCT]: {
- product_id: "prod_123",
- },
- [Modules.SALES_CHANNEL]: {
- sales_channel_id: "sc_123",
- },
-})
-```
-
-
-# Links between Promotion Module and Other Modules
-
-This document showcases the module links defined between the Promotion Module and other commerce modules.
-
-## Summary
-
-The Promotion Module has the following links to other modules:
-
-Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
-
-- [`Cart` data model of the Cart Module \<> `Promotion` data model](#cart-module).
-- [`LineItemAdjustment` data model of the Cart Module \<> `Promotion` data model](#cart-module). (Read-only).
-- [`Order` data model of the Order Module \<> `Promotion` data model](#order-module).
-
-***
-
-## Cart Module
-
-A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models.
-
-
-
-Medusa also defines a read-only link between the `Promotion` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model.
-
-### Retrieve with Query
-
-To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
-
-To retrieve the line item adjustments of a promotion, pass `line_item_adjustments.*` in `fields`.
-
-### query.graph
-
-```ts
-const { data: promotions } = await query.graph({
- entity: "promotion",
- fields: [
- "carts.*",
- ],
-})
-
-// promotions.carts
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: promotions } = useQueryGraphStep({
- entity: "promotion",
- fields: [
- "carts.*",
- ],
-})
-
-// promotions.carts
-```
-
-### Manage with Link
-
-To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.CART]: {
- cart_id: "cart_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-***
-
-## Order Module
-
-An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
-
-
-
-### Retrieve with Query
-
-To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
-
-### query.graph
-
-```ts
-const { data: promotions } = await query.graph({
- entity: "promotion",
- fields: [
- "orders.*",
- ],
-})
-
-// promotions.orders
-```
-
-### useQueryGraphStep
-
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-const { data: promotions } = useQueryGraphStep({
- entity: "promotion",
- fields: [
- "orders.*",
- ],
-})
-
-// promotions.orders
-```
-
-### Manage with Link
-
-To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
-
-### link.create
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-
-// ...
-
-await link.create({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-### createRemoteLinkStep
-
-```ts
-import { Modules } from "@medusajs/framework/utils"
-import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
-
-// ...
-
-createRemoteLinkStep({
- [Modules.ORDER]: {
- order_id: "order_123",
- },
- [Modules.PROMOTION]: {
- promotion_id: "promo_123",
- },
-})
-```
-
-
-# Campaign
-
-In this document, you'll learn about campaigns.
-
-## What is a Campaign?
-
-A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates.
-
-
-
-***
-
-## Campaign Limits
-
-Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used.
-
-There are two types of budgets:
-
-- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
-- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
-
-
-
-
-# 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.
+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.
@@ -22457,19 +20081,19 @@ Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A
### 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 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: priceSets } = await query.graph({
- entity: "price_set",
+const { data: shippingOptions } = await query.graph({
+ entity: "shipping_option",
fields: [
- "shipping_option.*",
+ "price_set.*",
],
})
-// priceSets.shipping_option
+// shippingOptions.price_set
```
### useQueryGraphStep
@@ -22479,14 +20103,14 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
+const { data: shippingOptions } = useQueryGraphStep({
+ entity: "shipping_option",
fields: [
- "shipping_option.*",
+ "price_set.*",
],
})
-// priceSets.shipping_option
+// shippingOptions.price_set
```
### Manage with Link
@@ -22532,31 +20156,25 @@ createRemoteLinkStep({
## Product Module
-The Product Module doesn't store or manage the prices of product variants.
+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.
-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.
+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 variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+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: priceSets } = await query.graph({
- entity: "price_set",
+const { data: shippingProfiles } = await query.graph({
+ entity: "shipping_profile",
fields: [
- "variant.*",
+ "products.*",
],
})
-// priceSets.variant
+// shippingProfiles.products
```
### useQueryGraphStep
@@ -22566,19 +20184,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
// ...
-const { data: priceSets } = useQueryGraphStep({
- entity: "price_set",
+const { data: shippingProfiles } = useQueryGraphStep({
+ entity: "shipping_profile",
fields: [
- "variant.*",
+ "products.*",
],
})
-// priceSets.variant
+// shippingProfiles.products
```
### 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 shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
### link.create
@@ -22589,10 +20207,10 @@ import { Modules } from "@medusajs/framework/utils"
await link.create({
[Modules.PRODUCT]: {
- variant_id: "variant_123",
+ product_id: "prod_123",
},
- [Modules.PRICING]: {
- price_set_id: "pset_123",
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
},
})
```
@@ -22607,164 +20225,407 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
createRemoteLinkStep({
[Modules.PRODUCT]: {
- variant_id: "variant_123",
+ product_id: "prod_123",
},
- [Modules.PRICING]: {
- price_set_id: "pset_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",
},
})
```
-# Price Rules
+# Shipping Option
-In this document, you'll learn about price rules for price sets and price lists.
+In this document, you’ll learn about shipping options and their rules.
-## Price Rule
+## What’s a Shipping Option?
-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).
+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.
-The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
+When the customer places their order, they choose a shipping option to be used to fulfill their items.
-For exmaple, you create a price restricted to `10557` zip codes.
-
-
-
-A price can have multiple price rules.
-
-For example, a price can be restricted by a region and a zip code.
-
-
+A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md).
***
-## Price List Rules
+## Service Zone Restrictions
-Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
+A shipping option is restricted by a service zone, limiting the locations a shipping option be used in.
-The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
+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.
-# Tax-Inclusive Pricing
-
-In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
-
-## What is Tax-Inclusive Pricing?
-
-A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
-
-For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
+
***
-## How is Tax-Inclusive Pricing Set?
+## Shipping Option Rules
-The [PricePreference data model](https://docs.medusajs.com/references/pricing/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
+You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
-- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
-- `value`: The attribute’s value. For example, `reg_123` or `usd`.
+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:
-Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
+- `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.
-The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
+
+
+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:
-```json
-{
- "attribute": "currency_code",
- "value": "USD",
- "is_tax_inclusive": true,
-}
+```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...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
```
-In this example, tax-inclusivity is enabled for the `USD` currency code.
+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.
+
+
+# Customer Accounts
+
+In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
+
+## `has_account` Property
+
+The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered.
+
+When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
+
+When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
***
-## Tax-Inclusive Pricing in Price Calculation
+## Email Uniqueness
-### Tax Context
+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.
-As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
-
-To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
-
-### Returned Tax Properties
-
-The `calculatePrices` method returns two properties related to tax-inclusivity:
-
-Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
-
-- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
-- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
-
-A price is considered tax-inclusive if:
-
-1. It belongs to the region or currency code specified in the calculation context;
-2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
-
-### Tax Context Precedence
-
-A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
-
-- both the `region_id` and `currency_code` are provided in the calculation context;
-- the selected price belongs to the region;
-- and the region has a price preference
+So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
-# Order Claim
+# Links between Customer Module and Other Modules
-In this document, you’ll learn about order claims.
+This document showcases the module links defined between the Customer Module and other commerce modules.
-## What is a Claim?
+## Summary
-When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item.
+The Customer Module has the following links to other modules:
-The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents 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.
+
+- [`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).
***
-## Claim Type
+## Payment Module
-The `Claim` data model has a `type` property whose value indicates the type of the claim:
+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.
-- `refund`: the items are returned, and the customer is refunded.
-- `replace`: the items are returned, and the customer receives new items.
+This link is available starting from Medusa `v2.5.0`.
+
+### Retrieve with Query
+
+To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: customers } = await query.graph({
+ entity: "customer",
+ fields: [
+ "account_holder.*",
+ ],
+})
+
+// customers.account_holder
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: [
+ "account_holder.*",
+ ],
+})
+
+// customers.account_holder
+```
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
***
-## Old and Replacement Items
+## Cart Module
-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.
+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.
-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
-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).
+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: customers } = await query.graph({
+ entity: "customer",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// customers.carts
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// customers.carts
+```
***
-## Claim Shipping Methods
+## Order Module
-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).
+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.
-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).
+### Retrieve with Query
-***
+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`:
-## Claim Refund
+### query.graph
-If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property.
+```ts
+const { data: customers } = await query.graph({
+ entity: "customer",
+ fields: [
+ "orders.*",
+ ],
+})
-The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim.
+// customers.orders
+```
-***
+### useQueryGraphStep
-## How Claims Impact an Order’s Version
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
-When a claim is confirmed, the order’s version is incremented.
+// ...
+
+const { data: customers } = useQueryGraphStep({
+ entity: "customer",
+ fields: [
+ "orders.*",
+ ],
+})
+
+// customers.orders
+```
# Order Edit
@@ -22822,6 +20683,107 @@ Once the Order Edit is confirmed, any additional payment or refund required can
This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md).
+# Order Claim
+
+In this document, you’ll learn about order claims.
+
+## 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 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 Exchange
In this document, you’ll learn about order exchanges.
@@ -22873,199 +20835,6 @@ Any payment or refund made is stored in the [Transaction data model](https://doc
When an exchange is confirmed, the order’s version is incremented.
-# Prices Calculation
-
-In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
-
-## calculatePrices Method
-
-The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
-
-It returns a price object with the best matching price for each price set.
-
-### Calculation Context
-
-The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
-
-For example:
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSetId] },
- {
- context: {
- currency_code: currencyCode,
- region_id: "reg_123",
- },
- }
-)
-```
-
-In this example, you retrieve the prices in a price set for the specified currency code and region ID.
-
-### Returned Price Object
-
-For each price set, the `calculatePrices` method selects two prices:
-
-- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
-- An original price, which is either:
- - The same price as the calculated price if the price list it belongs to is of type `override`;
- - Or a price that doesn't belong to a price list and best matches the specified context.
-
-Both prices are returned in an object that has the following properties:
-
-- id: (\`string\`) The ID of the price set from which the price was selected.
-- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
-- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
-- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
-- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
-- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
-- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
-- calculated\_price: (\`object\`) The calculated price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-- original\_price: (\`object\`) The original price's price details.
-
- - id: (\`string\`) The ID of the price.
-
- - price\_list\_id: (\`string\`) The ID of the associated price list.
-
- - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
-
- - min\_quantity: (\`number\`) The price's min quantity condition.
-
- - max\_quantity: (\`number\`) The price's max quantity condition.
-
-***
-
-## Examples
-
-Consider the following price set:
-
-```ts
-const priceSet = await pricingModuleService.createPriceSets({
- prices: [
- // default price
- {
- amount: 500,
- currency_code: "EUR",
- rules: {},
- },
- // prices with rules
- {
- amount: 400,
- currency_code: "EUR",
- rules: {
- region_id: "reg_123",
- },
- },
- {
- amount: 450,
- currency_code: "EUR",
- rules: {
- city: "krakow",
- },
- },
- {
- amount: 500,
- currency_code: "EUR",
- rules: {
- city: "warsaw",
- region_id: "reg_123",
- },
- },
- ],
-})
-```
-
-### Default Price Selection
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR"
- }
- }
-)
-```
-
-### Result
-
-### Calculate Prices with Rules
-
-### Code
-
-```ts
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "reg_123",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-### Price Selection with Price List
-
-### Code
-
-```ts
-const priceList = pricingModuleService.createPriceLists([{
- title: "Summer Price List",
- description: "Price list for summer sale",
- starts_at: Date.parse("01/10/2023").toString(),
- ends_at: Date.parse("31/10/2023").toString(),
- rules: {
- region_id: ['PL']
- },
- type: "sale",
- prices: [
- {
- amount: 400,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- {
- amount: 450,
- currency_code: "EUR",
- price_set_id: priceSet.id,
- },
- ],
-}]);
-
-const price = await pricingModuleService.calculatePrices(
- { id: [priceSet.id] },
- {
- context: {
- currency_code: "EUR",
- region_id: "PL",
- city: "krakow"
- }
- }
-)
-```
-
-### Result
-
-
# Links between Order Module and Other Modules
This document showcases the module links defined between the Order Module and other commerce modules.
@@ -23627,6 +21396,36 @@ The following table lists the possible `action` values that Medusa uses and what
|\`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 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.
+
+When changes are made on an order, such as an item is added or returned, the order's version changes.
+
+***
+
+## version Property
+
+The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
+
+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
+
+When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
+
+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 the order is retrieved, only the related data having the same version is retrieved.
+
+
# Promotions Adjustments in Orders
In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
@@ -23749,55 +21548,6 @@ await orderModuleService.setOrderShippingMethodAdjustments(
```
-# 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 Return
In this document, you’ll learn about order returns.
@@ -23886,36 +21636,6 @@ The following diagram is a simplified showcase of how a subtotal is calculated f
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`.
-# 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.
-
-When changes are made on an order, such as an item is added or returned, the order's version changes.
-
-***
-
-## version Property
-
-The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`.
-
-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
-
-When the order is changed, such as an item is exchanged, this changes the version of the order and its related data:
-
-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 the order is retrieved, only the related data having the same version is retrieved.
-
-
# Transactions
In this document, you’ll learn about an order’s transactions and its use.
@@ -23964,6 +21684,2322 @@ The `OrderTransaction` data model has two properties that determine which data m
- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
+# 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`:
+
+### query.graph
+
+```ts
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
+ ],
+})
+
+// priceSets.shipping_option
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
+ fields: [
+ "shipping_option.*",
+ ],
+})
+
+// priceSets.shipping_option
+```
+
+### Manage with Link
+
+To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.FULFILLMENT]: {
+ shipping_option_id: "so_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+***
+
+## Product Module
+
+The Product Module doesn't store or manage the prices of product variants.
+
+Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
+
+
+
+So, when you want to add prices for a product variant, you create a price set and add the prices to it.
+
+You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
+
+### Retrieve with Query
+
+To retrieve the variant of a price set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: priceSets } = await query.graph({
+ entity: "price_set",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// priceSets.variant
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: priceSets } = useQueryGraphStep({
+ entity: "price_set",
+ fields: [
+ "variant.*",
+ ],
+})
+
+// priceSets.variant
+```
+
+### Manage with Link
+
+To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+
+# 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.
+
+
+# Prices Calculation
+
+In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service.
+
+## calculatePrices Method
+
+The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context.
+
+It returns a price object with the best matching price for each price set.
+
+### Calculation Context
+
+The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
+
+For example:
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSetId] },
+ {
+ context: {
+ currency_code: currencyCode,
+ region_id: "reg_123",
+ },
+ }
+)
+```
+
+In this example, you retrieve the prices in a price set for the specified currency code and region ID.
+
+### Returned Price Object
+
+For each price set, the `calculatePrices` method selects two prices:
+
+- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
+- An original price, which is either:
+ - The same price as the calculated price if the price list it belongs to is of type `override`;
+ - Or a price that doesn't belong to a price list and best matches the specified context.
+
+Both prices are returned in an object that has the following properties:
+
+- id: (\`string\`) The ID of the price set from which the price was selected.
+- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list.
+- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer.
+- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list.
+- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value.
+- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price.
+- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx)
+- calculated\_price: (\`object\`) The calculated price's price details.
+
+ - id: (\`string\`) The ID of the price.
+
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
+
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
+
+ - min\_quantity: (\`number\`) The price's min quantity condition.
+
+ - max\_quantity: (\`number\`) The price's max quantity condition.
+- original\_price: (\`object\`) The original price's price details.
+
+ - id: (\`string\`) The ID of the price.
+
+ - price\_list\_id: (\`string\`) The ID of the associated price list.
+
+ - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`.
+
+ - min\_quantity: (\`number\`) The price's min quantity condition.
+
+ - max\_quantity: (\`number\`) The price's max quantity condition.
+
+***
+
+## Examples
+
+Consider the following price set:
+
+```ts
+const priceSet = await pricingModuleService.createPriceSets({
+ prices: [
+ // default price
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {},
+ },
+ // prices with rules
+ {
+ amount: 400,
+ currency_code: "EUR",
+ rules: {
+ region_id: "reg_123",
+ },
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ rules: {
+ city: "krakow",
+ },
+ },
+ {
+ amount: 500,
+ currency_code: "EUR",
+ rules: {
+ city: "warsaw",
+ region_id: "reg_123",
+ },
+ },
+ ],
+})
+```
+
+### Default Price Selection
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR"
+ }
+ }
+)
+```
+
+### Result
+
+### Calculate Prices with Rules
+
+### Code
+
+```ts
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "reg_123",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+### Price Selection with Price List
+
+### Code
+
+```ts
+const priceList = pricingModuleService.createPriceLists([{
+ title: "Summer Price List",
+ description: "Price list for summer sale",
+ starts_at: Date.parse("01/10/2023").toString(),
+ ends_at: Date.parse("31/10/2023").toString(),
+ rules: {
+ region_id: ['PL']
+ },
+ type: "sale",
+ prices: [
+ {
+ amount: 400,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ {
+ amount: 450,
+ currency_code: "EUR",
+ price_set_id: priceSet.id,
+ },
+ ],
+}]);
+
+const price = await pricingModuleService.calculatePrices(
+ { id: [priceSet.id] },
+ {
+ context: {
+ currency_code: "EUR",
+ region_id: "PL",
+ city: "krakow"
+ }
+ }
+)
+```
+
+### Result
+
+
+# Price Rules
+
+In this document, you'll learn about price rules for price sets and price lists.
+
+## Price Rule
+
+You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md).
+
+The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
+
+For exmaple, you create a price restricted to `10557` zip codes.
+
+
+
+A price can have multiple price rules.
+
+For example, a price can be restricted by a region and a zip code.
+
+
+
+***
+
+## Price List Rules
+
+Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md).
+
+The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
+
+
+
+
+# Tax-Inclusive Pricing
+
+In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices.
+
+## What is Tax-Inclusive Pricing?
+
+A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it.
+
+For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1.
+
+***
+
+## How is Tax-Inclusive Pricing Set?
+
+The [PricePreference data model](https://docs.medusajs.com/references/pricing/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context:
+
+- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`.
+- `value`: The attribute’s value. For example, `reg_123` or `usd`.
+
+Only `region_id` and `currency_code` are supported as an `attribute` at the moment.
+
+The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context.
+
+For example:
+
+```json
+{
+ "attribute": "currency_code",
+ "value": "USD",
+ "is_tax_inclusive": true,
+}
+```
+
+In this example, tax-inclusivity is enabled for the `USD` currency code.
+
+***
+
+## Tax-Inclusive Pricing in Price Calculation
+
+### Tax Context
+
+As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context.
+
+To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context.
+
+### Returned Tax Properties
+
+The `calculatePrices` method returns two properties related to tax-inclusivity:
+
+Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md).
+
+- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
+- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
+
+A price is considered tax-inclusive if:
+
+1. It belongs to the region or currency code specified in the calculation context;
+2. and the region or currency code has a price preference with `is_tax_inclusive` enabled.
+
+### Tax Context Precedence
+
+A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
+
+- both the `region_id` and `currency_code` are provided in the calculation context;
+- the selected price belongs to the region;
+- and the region has a price preference
+
+
+# Account Holders and Saved Payment Methods
+
+In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers.
+
+Account holders are available starting from Medusa `v2.5.0`.
+
+## What's an Account Holder?
+
+An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model.
+
+It holds fields retrieved from the third-party provider, such as:
+
+- `external_id`: The ID of the equivalent customer or account holder in the third-party provider.
+- `data`: Data returned by the payment provider when the account holder is created.
+
+A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider.
+
+***
+
+## Save Payment Methods
+
+If a payment provider supports saving payment methods for a customer, they must implement the following methods:
+
+- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record.
+- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa.
+- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider.
+- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront.
+
+Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md).
+
+***
+
+## Account Holder in Medusa Payment Flows
+
+In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer.
+
+Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa.
+
+This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods).
+
+
+# Links between Currency Module and Other Modules
+
+This document showcases the module links defined between the Currency Module and other commerce modules.
+
+## Summary
+
+The Currency Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+- [`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
+```
+
+
+# Payment
+
+In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
+
+## What's a Payment?
+
+When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded.
+
+A payment carries many of the data and relations of a payment session:
+
+- It belongs to the same payment collection.
+- It’s associated with the same payment provider, which handles further payment processing.
+- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
+
+***
+
+## Capture Payments
+
+When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more.
+
+The payment can also be captured incrementally, each time a capture record is created for that amount.
+
+
+
+***
+
+## Refund Payments
+
+When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more.
+
+A payment can be refunded multiple times, and each time a refund record is created.
+
+
+
+
+# Payment Module Options
+
+In this document, you'll learn about the options of the Payment Module.
+
+## All Module Options
+
+|Option|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`|
+|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`|
+|\`providers\`|An array of payment providers to install and register. Learn more |No|-|
+
+***
+
+## providers Option
+
+The `providers` option is an array of payment module providers.
+
+When the Medusa application starts, these providers are registered and can be used to process payments.
+
+For example:
+
+```ts title="medusa-config.ts"
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "@medusajs/medusa/payment",
+ options: {
+ providers: [
+ {
+ resolve: "@medusajs/medusa/payment-stripe",
+ id: "stripe",
+ options: {
+ // ...
+ },
+ },
+ ],
+ },
+ },
+ ],
+})
+```
+
+The `providers` option is an array of objects that accept the following properties:
+
+- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
+- `id`: A string indicating the provider's unique name or ID.
+- `options`: An optional object of the module provider's options.
+
+
+# Links between Payment Module and Other Modules
+
+This document showcases the module links defined between the Payment Module and other commerce modules.
+
+## Summary
+
+The Payment Module has the following links to other modules:
+
+- [`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).
+
+***
+
+## Cart Module
+
+The Cart Module provides cart-related features, but not payment processing.
+
+Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
+
+Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "cart.*",
+ ],
+})
+
+// paymentCollections.cart
+```
+
+### Manage with Link
+
+To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Customer Module
+
+Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it.
+
+This link is available starting from Medusa `v2.5.0`.
+
+### Retrieve with Query
+
+To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: accountHolders } = await query.graph({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: accountHolders } = useQueryGraphStep({
+ entity: "account_holder",
+ fields: [
+ "customer.*",
+ ],
+})
+
+// accountHolders.customer
+```
+
+### Manage with Link
+
+To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CUSTOMER]: {
+ customer_id: "cus_123",
+ },
+ [Modules.PAYMENT]: {
+ account_holder_id: "acchld_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+An order's payment details are stored in a payment collection. This also applies for claims and exchanges.
+
+So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentCollections } = await query.graph({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentCollections } = useQueryGraphStep({
+ entity: "payment_collection",
+ fields: [
+ "order.*",
+ ],
+})
+
+// paymentCollections.order
+```
+
+### Manage with Link
+
+To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_collection_id: "paycol_123",
+ },
+})
+```
+
+***
+
+## Region Module
+
+You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models.
+
+
+
+This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region.
+
+### Retrieve with Query
+
+To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: paymentProviders } = await query.graph({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: paymentProviders } = useQueryGraphStep({
+ entity: "payment_provider",
+ fields: [
+ "regions.*",
+ ],
+})
+
+// paymentProviders.regions
+```
+
+### Manage with Link
+
+To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.REGION]: {
+ region_id: "reg_123",
+ },
+ [Modules.PAYMENT]: {
+ payment_provider_id: "pp_stripe_stripe",
+ },
+})
+```
+
+
+# Payment Collection
+
+In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module.
+
+## What's a Payment Collection?
+
+A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md).
+
+Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
+
+- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize.
+- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded.
+- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund.
+
+***
+
+## Multiple Payments
+
+The payment collection supports multiple payment sessions and payments.
+
+You can use this to accept payments in increments or split payments across payment providers.
+
+
+
+***
+
+## Usage with the Cart Module
+
+The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
+
+During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing.
+
+It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md).
+
+
+
+
+# Accept Payment Flow
+
+In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service.
+
+It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases.
+
+For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md).
+
+## Flow Overview
+
+
+
+***
+
+## 1. Create a Payment Collection
+
+A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+await createPaymentCollectionForCartWorkflow(req.scope)
+ .run({
+ input: {
+ cart_id: "cart_123",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentCollection =
+ await paymentModuleService.createPaymentCollections({
+ currency_code: "usd",
+ amount: 5000,
+ })
+```
+
+***
+
+## 2. Create Payment Sessions
+
+The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
+
+So, after creating the payment collection, create at least one payment session for a provider.
+
+For example:
+
+### Using Workflow
+
+```ts
+import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope)
+ .run({
+ input: {
+ payment_collection_id: "paycol_123",
+ provider_id: "stripe",
+ },
+ })
+```
+
+### Using Service
+
+```ts
+const paymentSession =
+ await paymentModuleService.createPaymentSession(
+ paymentCollection.id,
+ {
+ provider_id: "stripe",
+ currency_code: "usd",
+ amount: 5000,
+ data: {
+ // any necessary data for the
+ // payment provider
+ },
+ }
+ )
+```
+
+***
+
+## 3. Authorize Payment Session
+
+Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
+
+For example:
+
+### Using Step
+
+```ts
+import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+### Using Service
+
+```ts
+const payment = authorizePaymentSessionStep({
+ id: "payses_123",
+ context: {},
+})
+```
+
+When the payment authorization is successful, a payment is created and returned.
+
+### Handling Additional Action
+
+If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step.
+
+If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
+
+In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization.
+
+For example:
+
+```ts
+try {
+ const payment =
+ await paymentModuleService.authorizePaymentSession(
+ paymentSession.id,
+ {}
+ )
+} catch (e) {
+ // retrieve the payment session again
+ const updatedPaymentSession = (
+ await paymentModuleService.listPaymentSessions({
+ id: [paymentSession.id],
+ })
+ )[0]
+
+ if (updatedPaymentSession.status === "requires_more") {
+ // TODO perform required action
+ // TODO authorize payment again.
+ }
+}
+```
+
+***
+
+## 4. Payment Flow Complete
+
+The payment flow is complete once the payment session is authorized and the payment is created.
+
+You can then:
+
+- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md).
+- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md).
+
+Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
+
+
+# Payment Module Provider
+
+In this document, you’ll learn what a payment module provider is.
+
+## What's a Payment Module Provider?
+
+A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe.
+
+To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
+
+After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
+
+### List of Payment Module Providers
+
+- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md)
+
+***
+
+## System Payment Provider
+
+The Payment Module provides a `system` payment provider that acts as a placeholder payment provider.
+
+It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
+
+***
+
+## How are Payment Providers Created?
+
+A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`.
+
+Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module.
+
+***
+
+## Configure Payment Providers
+
+The Payment Module accepts a `providers` option that allows you to register providers in your application.
+
+Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md).
+
+***
+
+## PaymentProvider Data Model
+
+When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists.
+
+This data model is used to reference a payment provider and determine whether it’s installed in the application.
+
+
+# Webhook Events
+
+In this document, you’ll learn how the Payment Module supports listening to webhook events.
+
+## What's a Webhook Event?
+
+A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status.
+
+This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later.
+
+***
+
+## getWebhookActionAndData Method
+
+The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details.
+
+Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where:
+
+- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`.
+- `[provider]` is the ID of the provider. For example, `stripe`.
+
+For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`.
+
+Use that webhook listener in your third-party payment provider's configurations.
+
+
+
+If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session.
+
+If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session.
+
+### Actions After Webhook Payment Processing
+
+After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet.
+
+
+# Payment Session
+
+In this document, you’ll learn what a payment session is.
+
+## What's a Payment Session?
+
+A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
+
+A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers.
+
+
+
+***
+
+## data Property
+
+Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
+
+For example, the customer's ID in Stripe is stored in the `data` property.
+
+***
+
+## Payment Session Status
+
+The `status` property of a payment session indicates its current status. Its value can be:
+
+- `pending`: The payment session is awaiting authorization.
+- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
+- `authorized`: The payment session is authorized.
+- `error`: An error occurred while authorizing the payment.
+- `canceled`: The authorization of the payment session has been canceled.
+
+
+# Configure Selling Products
+
+In this guide, you'll learn how to set up and configure your products based on their shipping and inventory requirements, the product type, how you want to sell them, or your commerce ecosystem.
+
+The concepts in this guide are applicable starting from Medusa v2.5.1.
+
+## Scenario
+
+Businesses can have different selling requirements:
+
+1. They may sell physical or digital items.
+2. They may sell items that don't require shipping or inventory management, such as selling digital products, services, or booking appointments.
+3. They may sell items whose inventory is managed by an external system, such as an ERP.
+
+Medusa supports these different selling requirements by allowing you to configure shipping and inventory requirements for products and their variants. This guide explains how these configurations work, then provides examples of setting up different use cases.
+
+***
+
+## Configuring Shipping Requirements
+
+The Medusa application defines a link between the `Product` data model and a [ShippingProfile](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts#shipping-profile/index.html.md) in the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md), allowing you to associate a product with a shipping profile.
+
+When a product is associated with a shipping profile, its variants require shipping and fulfillment when purchased. This is useful for physical products or digital products that require custom fulfillment.
+
+If a product doesn't have an associated shipping profile, its variants don't require shipping and fulfillment when purchased. This is useful for digital products, for example, that don't require shipping.
+
+### Overriding Shipping Requirements for Variants
+
+A product variant whose inventory is managed by Medusa (its `manage_inventory` property is enabled) has an [inventory item](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts#inventoryitem/index.html.md). The inventory item has a `requires_shipping` property that can be used to override its shipping requirement. This is useful if the product has an associated shipping profile but you want to disable shipping for a specific variant, or vice versa.
+
+Learn more about product variant's inventory in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
+
+When a product variant is purchased, the Medusa application decides whether the purchased item requires shipping in the following order:
+
+1. The product variant has an inventory item. In this case, the Medusa application uses the inventory item's `requires_shipping` property to determine if the item requires shipping.
+2. If the product variant doesn't have an inventory item, the Medusa application checks whether the product has an associated shipping profile to determine if the item requires shipping.
+
+***
+
+## Use Case Examples
+
+By combining configurations of shipment requirements and inventory management, you can set up your products to support your use case:
+
+|Use Case|Configurations|Example|
+|---|---|---|---|---|
+|Item that's shipped on purchase, and its variant inventory is managed by the Medusa application.||Any stock-kept item (clothing, for example), whose inventory is managed in the Medusa application.|
+|Item that's shipped on purchase, but its variant inventory is managed externally (not by Medusa) or it has infinite stock.||Any stock-kept item (clothing, for example), whose inventory is managed in an ERP or has infinite stock.|
+|Item that's not shipped on purchase, but its variant inventory is managed by Medusa.||Digital products, such as licenses, that don't require shipping but have a limited quantity.|
+|Item that doesn't require shipping and its variant inventory isn't managed by Medusa.|||
+
+
+# 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
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "line_items.*",
+ ],
+})
+
+// variants.line_items
+```
+
+***
+
+## Fulfillment Module
+
+Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile.
+
+This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0).
+
+### Retrieve with Query
+
+To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "shipping_profile.*",
+ ],
+})
+
+// products.shipping_profile
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: [
+ "shipping_profile.*",
+ ],
+})
+
+// products.shipping_profile
+```
+
+### Manage with Link
+
+To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.FULFILLMENT]: {
+ shipping_profile_id: "sp_123",
+ },
+})
+```
+
+***
+
+## Inventory Module
+
+The Inventory Module provides inventory-management features for any stock-kept item.
+
+Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details.
+
+
+
+When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
+
+Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md).
+
+### Retrieve with Query
+
+To retrieve the inventory items of a product variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_items.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "inventory_items.*",
+ ],
+})
+
+// variants.inventory_items
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "inventory_items.*",
+ ],
+})
+
+// variants.inventory_items
+```
+
+### Manage with Link
+
+To manage the inventory items of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.INVENTORY]: {
+ inventory_item_id: "iitem_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+Medusa defines read-only links between:
+
+- the `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.
+
+### 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`:
+
+### query.graph
+
+```ts
+const { data: variants } = await query.graph({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// variants.price_set
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: variants } = useQueryGraphStep({
+ entity: "variant",
+ fields: [
+ "price_set.*",
+ ],
+})
+
+// variants.price_set
+```
+
+### Manage with Link
+
+To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ variant_id: "variant_123",
+ },
+ [Modules.PRICING]: {
+ price_set_id: "pset_123",
+ },
+})
+```
+
+***
+
+## Sales Channel Module
+
+The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
+
+Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
+
+
+
+### Retrieve with Query
+
+To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: products } = await query.graph({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// products.sales_channels
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: products } = useQueryGraphStep({
+ entity: "product",
+ fields: [
+ "sales_channels.*",
+ ],
+})
+
+// products.sales_channels
+```
+
+### Manage with Link
+
+To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.PRODUCT]: {
+ product_id: "prod_123",
+ },
+ [Modules.SALES_CHANNEL]: {
+ sales_channel_id: "sc_123",
+ },
+})
+```
+
+
+# Promotion Actions
+
+In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md).
+
+## computeActions Method
+
+The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied.
+
+Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
+
+***
+
+## Action Types
+
+### `addItemAdjustment` Action
+
+The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
+
+This action has the following format:
+
+```ts
+export interface AddItemAdjustmentAction {
+ action: "addItemAdjustment"
+ item_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `removeItemAdjustment` Action
+
+The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
+
+The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveItemAdjustmentAction {
+ action: "removeItemAdjustment"
+ adjustment_id: string
+ description?: string
+ code: string
+}
+```
+
+This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties.
+
+### `addShippingMethodAdjustment` Action
+
+The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
+
+This action has the following format:
+
+```ts
+export interface AddShippingMethodAdjustment {
+ action: "addShippingMethodAdjustment"
+ shipping_method_id: string
+ amount: number
+ code: string
+ description?: string
+}
+```
+
+This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `removeShippingMethodAdjustment` Action
+
+The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
+
+The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
+
+This action has the following format:
+
+```ts
+export interface RemoveShippingMethodAdjustment {
+ action: "removeShippingMethodAdjustment"
+ adjustment_id: string
+ code: string
+}
+```
+
+When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties.
+
+### `campaignBudgetExceeded` Action
+
+When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
+
+This action has the following format:
+
+```ts
+export interface CampaignBudgetExceededAction {
+ action: "campaignBudgetExceeded"
+ code: string
+}
+```
+
+Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties.
+
+
+# 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.
+
+## What is a Campaign?
+
+A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates.
+
+
+
+***
+
+## Campaign Limits
+
+Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used.
+
+There are two types of budgets:
+
+- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
+- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
+
+
+
+
+# Promotion Concepts
+
+In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module.
+
+## What is a Promotion?
+
+A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders.
+
+A promotion has two types:
+
+- `standard`: A standard promotion with rules.
+- `buyget`: “A buy X get Y” promotion with rules.
+
+|\`standard\`|\`buyget\`|
+|---|---|
+|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.|
+|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.|
+|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.|
+
+The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes.
+
+***
+
+## PromotionRule
+
+A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md).
+
+For example, you can create a promotion that only customers of the `VIP` customer group can use.
+
+
+
+A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied.
+
+For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
+
+When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself.
+
+For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
+
+***
+
+## Flexible Rules
+
+The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`).
+
+For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
+
+- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
+- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`.
+
+
+
+In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion.
+
+
+# Links between Promotion Module and Other Modules
+
+This document showcases the module links defined between the Promotion Module and other commerce modules.
+
+## Summary
+
+The Promotion Module has the following links to other modules:
+
+Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database.
+
+- [`Cart` data model of the Cart Module \<> `Promotion` data model](#cart-module).
+- [`LineItemAdjustment` data model of the Cart Module \<> `Promotion` data model](#cart-module). (Read-only).
+- [`Order` data model of the Order Module \<> `Promotion` data model](#order-module).
+
+***
+
+## Cart Module
+
+A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models.
+
+
+
+Medusa also defines a read-only link between the `Promotion` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model.
+
+### Retrieve with Query
+
+To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`:
+
+To retrieve the line item adjustments of a promotion, pass `line_item_adjustments.*` in `fields`.
+
+### query.graph
+
+```ts
+const { data: promotions } = await query.graph({
+ entity: "promotion",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// promotions.carts
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: promotions } = useQueryGraphStep({
+ entity: "promotion",
+ fields: [
+ "carts.*",
+ ],
+})
+
+// promotions.carts
+```
+
+### Manage with Link
+
+To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.CART]: {
+ cart_id: "cart_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+***
+
+## Order Module
+
+An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.
+
+
+
+### Retrieve with Query
+
+To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`:
+
+### query.graph
+
+```ts
+const { data: promotions } = await query.graph({
+ entity: "promotion",
+ fields: [
+ "orders.*",
+ ],
+})
+
+// promotions.orders
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: promotions } = useQueryGraphStep({
+ entity: "promotion",
+ fields: [
+ "orders.*",
+ ],
+})
+
+// promotions.orders
+```
+
+### Manage with Link
+
+To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md):
+
+### link.create
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+
+// ...
+
+await link.create({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+### createRemoteLinkStep
+
+```ts
+import { Modules } from "@medusajs/framework/utils"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+createRemoteLinkStep({
+ [Modules.ORDER]: {
+ order_id: "order_123",
+ },
+ [Modules.PROMOTION]: {
+ promotion_id: "promo_123",
+ },
+})
+```
+
+
+# Product Variant Inventory
+
+# Product Variant Inventory
+
+In this guide, you'll learn about the inventory management features related to product variants.
+
+## Configure Inventory Management of Product Variants
+
+A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant in the Medusa application. You can also keep `manage_inventory` disabled if you manage the product's inventory in an external system, such as an ERP.
+
+The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity.
+
+When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant.
+
+***
+
+## How the Medusa Application Manages Inventory
+
+When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant.
+
+
+
+The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe.
+
+
+
+Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md).
+
+The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level.
+
+
+
+Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md).
+
+### Product Inventory in Storefronts
+
+When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront.
+
+The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations.
+
+For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel.
+
+
+
+***
+
+## Variant Back Orders
+
+Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase.
+
+You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md).
+
+***
+
+## Additional Resources
+
+The following guides provide more details on inventory management in the Medusa application:
+
+- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module.
+- [Configure Selling Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/selling-products/index.html.md): Learn how to use inventory management to support different use cases when selling products.
+- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows.
+- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md).
+
+
# Links between Sales Channel Module and Other Modules
This document showcases the module links defined between the Sales Channel Module and other commerce modules.
@@ -24334,6 +24370,306 @@ The Medusa application infers the associated sales channels and ensures that onl
To create a publishable API key, either use the Medusa Admin 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",
+ },
+})
+```
+
+
+# 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
+```
+
+### useQueryGraphStep
+
+```ts
+import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+
+// ...
+
+const { data: stores } = useQueryGraphStep({
+ entity: "store",
+ fields: [
+ "supported_currencies.currency.*",
+ ],
+})
+
+// stores.supported_currencies
+```
+
+
# Links between Region Module and Other Modules
This document showcases the module links defined between the Region Module and other commerce modules.
@@ -24747,6 +25083,19 @@ TODO add once tax provider guide is updated + add module providers match other m
Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */}
+# Tax Region
+
+In this document, you’ll learn about tax regions and how to use them with the Region Module.
+
+## What is a Tax Region?
+
+A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
+
+Tax regions can inherit settings and rules from a parent tax region.
+
+Each tax region has tax rules and a tax provider.
+
+
# Tax Rates and Rules
In this document, you’ll learn about tax rates and rules.
@@ -24783,317 +25132,235 @@ These two properties of the data model identify the rule’s target:
So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type.
-# Tax Region
+# Google Auth Module Provider
-In this document, you’ll learn about tax regions and how to use them with the Region Module.
+In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
-## What is a Tax Region?
+The Google Auth Module Provider authenticates users with their Google account.
-A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves.
-
-Tax regions can inherit settings and rules from a parent tax region.
-
-Each tax region has tax rules and a tax provider.
-
-
-# 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).
+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).
***
-## Currency Module
+## Register the Google Auth Module Provider
-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.
+### Prerequisites
-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.
+- [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)
-### Retrieve with Query
+Add the module to the array of providers passed to the Auth Module:
-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"
+```ts title="medusa-config.ts"
+import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
// ...
-const { data: stores } = useQueryGraphStep({
- entity: "store",
- fields: [
- "supported_currencies.currency.*",
+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,
+ },
+ },
+ ],
+ },
+ },
+ },
],
})
-
-// stores.supported_currencies
```
+### Environment Variables
-# Stock Location Concepts
+Make sure to add the necessary environment variables for the above options in `.env`:
-In this document, you’ll learn about the main concepts in the Stock Location Module.
+```plain
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+GOOGLE_CALLBACK_URL=
+```
-## Stock Location
+### Module Options
-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.
+|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|
***
-## 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.
+## 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.
-# 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).
+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).
***
-## Fulfillment Module
+## Examples
-A fulfillment set can be conditioned to a specific stock location.
+- [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).
-Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models.
-
+# GitHub Auth Module Provider
-Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
+In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
-
+The Github Auth Module Provider authenticates users with their GitHub account.
-### 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",
- },
-})
-```
+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).
***
-## Inventory Module
+## Register the Github Auth Module Provider
-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.
+### Prerequisites
-### Retrieve with Query
+- [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)
-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`:
+Add the module to the array of providers passed to the Auth Module:
-### 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"
+```ts title="medusa-config.ts"
+import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
// ...
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "inventory_levels.*",
+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,
+ },
+ },
+ ],
+ },
+ },
],
})
-
-// stockLocations.inventory_levels
```
+### 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|
+
***
-## Sales Channel Module
+## Override Callback URL During Authentication
-A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel.
+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.
-Medusa defines a link between the `SalesChannel` and `StockLocation` data models.
+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).
-
+***
-### Retrieve with Query
+## Examples
-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`:
+- [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).
-### query.graph
-```ts
-const { data: stockLocations } = await query.graph({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
+# Emailpass Auth Module Provider
+
+In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
+
+Using the Emailpass auth module provider, you allow users to register and login with an email and password.
+
+***
+
+## 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, 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-emailpass",
+ id: "emailpass",
+ options: {
+ // options...
+ },
+ },
+ ],
+ },
+ },
],
})
-
-// stockLocations.sales_channels
```
-### useQueryGraphStep
+### Module Options
-```ts
-import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
+|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
+}
+\`\`\`|
-// ...
+***
-const { data: stockLocations } = useQueryGraphStep({
- entity: "stock_location",
- fields: [
- "sales_channels.*",
- ],
-})
+## Related Guides
-// 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",
- },
-})
-```
+- [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)
# Stripe Module Provider
@@ -25196,155 +25463,6 @@ 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).
-# Emailpass Auth Module Provider
-
-In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module.
-
-Using the Emailpass auth module provider, you allow users to register and login with an email and password.
-
-***
-
-## 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, 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-emailpass",
- id: "emailpass",
- options: {
- // options...
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-### Module Options
-
-|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
-}
-\`\`\`|
-
-***
-
-## Related Guides
-
-- [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)
-
-
-# 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).
-
-
# 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).
@@ -25425,88 +25543,6 @@ For the context of the product variant's calculated price, you pass an object to
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).
-# GitHub Auth Module Provider
-
-In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
-
-The Github Auth Module Provider authenticates users with their GitHub account.
-
-Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md).
-
-***
-
-## Register the Github Auth Module Provider
-
-### Prerequisites
-
-- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app)
-- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication)
-
-Add the module to the array of providers passed to the Auth Module:
-
-```ts title="medusa-config.ts"
-import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
-
-// ...
-
-module.exports = defineConfig({
- // ...
- modules: [
- {
- resolve: "@medusajs/medusa/auth",
- dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
- options: {
- providers: [
- // other providers...
- {
- resolve: "@medusajs/medusa/auth-github",
- id: "github",
- options: {
- clientId: process.env.GITHUB_CLIENT_ID,
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
- callbackUrl: process.env.GITHUB_CALLBACK_URL,
- },
- },
- ],
- },
- },
- ],
-})
-```
-
-### Environment Variables
-
-Make sure to add the necessary environment variables for the above options in `.env`:
-
-```plain
-GITHUB_CLIENT_ID=
-GITHUB_CLIENT_SECRET=
-GITHUB_CALLBACK_URL=
-```
-
-### Module Options
-
-|Configuration|Description|Required|
-|---|---|---|---|---|
-|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes|
-|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes|
-|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes|
-
-***
-
-## Override Callback URL During Authentication
-
-In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication.
-
-The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md).
-
-***
-
-## Examples
-
-- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md).
-
-
# Calculate Product Variant Price with Taxes
In this document, you'll learn how to calculate a product variant's price with taxes.
@@ -25696,97 +25732,288 @@ For each product variant, you:
- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/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)
+- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md)
- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md)
- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md)
- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/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)
- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/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)
-- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/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)
+- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/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)
+- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md)
+- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/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)
- [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)
-- [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md)
-- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/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)
- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md)
-- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md)
-- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md)
-- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
-- [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/index.html.md)
-- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md)
-- [deleteInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInvitesWorkflow/index.html.md)
-- [refreshInviteTokensWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshInviteTokensWorkflow/index.html.md)
-- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/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)
-- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md)
-- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md)
-- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md)
-- [createCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomersWorkflow/index.html.md)
+- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md)
+- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/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)
+- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/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)
-- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/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)
+- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md)
+- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md)
+- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md)
+- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md)
+- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md)
- [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/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)
-- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/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)
-- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
-- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/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)
-- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/index.html.md)
- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md)
+- [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md)
+- [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md)
+- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md)
+- [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md)
+- [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md)
+- [deleteServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteServiceZonesWorkflow/index.html.md)
+- [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md)
+- [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md)
+- [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md)
+- [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/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)
- [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md)
- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/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)
-- [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/index.html.md)
+- [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/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)
+- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/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)
+- [createInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryLevelsWorkflow/index.html.md)
- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md)
- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md)
-- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md)
- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md)
+- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/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)
+- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
+- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
+- [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md)
+- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
+- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md)
+- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
+- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
+- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
+- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md)
+- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
+- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md)
+- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md)
+- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md)
+- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md)
+- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md)
+- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
+- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
+- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md)
+- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
+- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
+- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
+- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md)
+- [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/index.html.md)
+- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md)
+- [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/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)
+- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/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)
+- [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/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)
+- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md)
+- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md)
+- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
+- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md)
+- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md)
+- [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md)
+- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md)
+- [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md)
+- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
+- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
+- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
+- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md)
+- [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md)
+- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md)
+- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md)
+- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md)
+- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
+- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
+- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
+- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
+- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
+- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
+- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
+- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
+- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md)
+- [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md)
+- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
+- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
+- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
+- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
+- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
+- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
+- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
+- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
+- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
+- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/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)
+- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md)
+- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/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)
+- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md)
+- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/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)
+- [dismissItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestValidationStep/index.html.md)
+- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md)
+- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md)
+- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md)
+- [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md)
+- [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md)
+- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
+- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md)
+- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
+- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
+- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
+- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/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)
+- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/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)
+- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md)
+- [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md)
+- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
+- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md)
+- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md)
+- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
+- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
+- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md)
+- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md)
+- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
+- [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md)
+- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md)
+- [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md)
+- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
+- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
+- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
+- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md)
+- [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md)
+- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md)
+- [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md)
+- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md)
+- [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md)
+- [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md)
+- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
+- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
+- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md)
+- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md)
+- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md)
+- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md)
+- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/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)
+- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/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)
+- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md)
+- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/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)
+- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/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)
+- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
+- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
+- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md)
+- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md)
+- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md)
+- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md)
+- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md)
+- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md)
+- [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md)
+- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md)
+- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/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)
+- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
+- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
+- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md)
+- [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md)
+- [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/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)
+- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/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)
+- [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)
+- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md)
+- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
+- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md)
+- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md)
+- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/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)
+- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
+- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/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)
+- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/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)
+- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
+- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
+- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
+- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md)
+- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md)
+- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md)
+- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md)
+- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
+- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md)
+- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md)
- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md)
+- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md)
- [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md)
- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md)
-- [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)
- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/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)
+- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md)
+- [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md)
- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md)
+- [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md)
+- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/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)
- [deleteProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTagsWorkflow/index.html.md)
-- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/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)
- [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md)
-- [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md)
+- [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md)
- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md)
+- [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md)
+- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md)
- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md)
- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md)
- [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md)
@@ -25794,500 +26021,309 @@ For each product variant, you:
- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/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)
-- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md)
-- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md)
-- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md)
-- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/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)
-- [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md)
-- [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md)
-- [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md)
-- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md)
-- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md)
-- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/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)
-- [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/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)
-- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md)
-- [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md)
-- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md)
-- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md)
-- [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md)
-- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/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)
-- [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md)
-- [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/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)
-- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md)
-- [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md)
-- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md)
-- [cancelOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderTransferRequestWorkflow/index.html.md)
-- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/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)
-- [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/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)
-- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md)
-- [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/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)
-- [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md)
-- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md)
-- [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md)
-- [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/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)
-- [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md)
-- [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md)
-- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md)
-- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md)
-- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md)
-- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md)
-- [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md)
-- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md)
-- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md)
-- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md)
-- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md)
-- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md)
-- [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md)
-- [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md)
-- [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md)
-- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md)
-- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md)
-- [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md)
-- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md)
-- [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md)
-- [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md)
-- [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md)
-- [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md)
-- [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md)
-- [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/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)
-- [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)
-- [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md)
-- [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md)
-- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/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)
-- [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md)
-- [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md)
-- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md)
-- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md)
-- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md)
-- [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md)
-- [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md)
-- [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md)
-- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md)
-- [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/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)
-- [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md)
-- [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/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)
-- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md)
-- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md)
-- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md)
-- [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md)
-- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md)
-- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md)
-- [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md)
-- [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md)
-- [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md)
-- [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md)
-- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md)
-- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md)
-- [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)
-- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md)
-- [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md)
-- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md)
-- [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/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)
-- [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md)
-- [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md)
-- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/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)
-- [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)
-- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md)
-- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md)
-- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md)
-- [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md)
-- [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md)
-- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/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)
-- [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md)
-- [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md)
-- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/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)
-- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md)
-- [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md)
-- [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md)
-- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/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)
-- [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/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)
-- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md)
-- [updateReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnWorkflow/index.html.md)
-- [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md)
-- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md)
-- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md)
- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md)
-- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md)
-- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md)
-- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md)
-- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md)
-- [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md)
-- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md)
-- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md)
-- [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/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)
-- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md)
-- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md)
-- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/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)
-- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md)
-- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md)
-- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md)
-- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md)
-- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md)
-- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md)
-- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md)
-- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md)
-- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md)
-- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md)
-- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/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)
+- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/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)
+- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
- [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md)
- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md)
+- [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)
- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md)
- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md)
-- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md)
-- [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md)
- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md)
+- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md)
- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md)
- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md)
- [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)
-- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md)
-- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md)
-- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md)
-- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md)
-- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/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)
+- [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)
+- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md)
+- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
+- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md)
+- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md)
+- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md)
+- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
+- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md)
+- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md)
+- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
+- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
- [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)
- [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md)
- [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md)
-- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md)
-- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md)
-- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md)
-- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md)
-- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/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)
-- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md)
-- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md)
-- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/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)
-- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/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)
## 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)
-- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/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)
+- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/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)
- [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md)
- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md)
- [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md)
- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md)
-- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/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)
- [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md)
+- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md)
- [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/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)
+- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
- [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md)
- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md)
-- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md)
-- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md)
- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md)
- [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md)
+- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/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)
- [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md)
+- [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/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)
-- [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)
+- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md)
- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/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)
+- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/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)
-- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md)
-- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md)
-- [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md)
- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md)
+- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md)
- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md)
- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/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)
- [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)
- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md)
-- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
-- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
-- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
-- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
-- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
-- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md)
-- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
-- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md)
-- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md)
-- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
-- [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)
+- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/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)
- [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md)
- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md)
- [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md)
-- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md)
-- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md)
-- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md)
-- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md)
-- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
-- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
-- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md)
-- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
-- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
-- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md)
-- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md)
-- [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)
-- [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)
-- [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md)
-- [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md)
-- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md)
+- [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)
+- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md)
+- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md)
+- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md)
+- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md)
+- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md)
+- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md)
+- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/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)
- [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)
+- [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/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)
+- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/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)
-- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md)
+- [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md)
- [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md)
-- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
+- [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)
- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md)
+- [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md)
- [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md)
- [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md)
- [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md)
-- [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md)
+- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/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)
+- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/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)
+- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md)
+- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md)
+- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md)
+- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md)
+- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md)
+- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md)
+- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/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)
+- [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md)
+- [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)
+- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/index.html.md)
+- [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md)
+- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md)
+- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/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)
-- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
-- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
- [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md)
- [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md)
+- [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md)
+- [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md)
- [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)
-- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/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)
+- [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)
+- [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)
-- [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md)
-- [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md)
- [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md)
+- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
+- [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md)
- [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)
+- [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)
+- [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md)
+- [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md)
- [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md)
-- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md)
-- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
-- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
- [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md)
+- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md)
+- [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md)
+- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md)
- [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md)
- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md)
-- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
- [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md)
- [updateOrderShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderShippingMethodsStep/index.html.md)
-- [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md)
+- [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md)
- [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md)
+- [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md)
- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md)
-- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md)
-- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/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)
-- [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)
-- [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)
- [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)
- [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)
- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md)
-- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md)
-- [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/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)
-- [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md)
-- [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md)
-- [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/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)
-- [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md)
-- [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md)
-- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md)
-- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
-- [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md)
-- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/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)
-- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
-- [deleteProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductVariantsStep/index.html.md)
-- [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md)
-- [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/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)
-- [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/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)
-- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/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)
-- [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md)
-- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
-- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md)
+- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md)
- [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/index.html.md)
- [removePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/removePriceListPricesStep/index.html.md)
-- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md)
+- [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/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)
- [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)
-- [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)
+- [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/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)
+- [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md)
+- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/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)
+- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md)
+- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md)
+- [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md)
+- [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/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)
+- [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/index.html.md)
+- [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/index.html.md)
+- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md)
+- [getAllProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getAllProductsStep/index.html.md)
+- [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/index.html.md)
+- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/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)
+- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/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)
+- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md)
+- [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/index.html.md)
+- [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md)
+- [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)
-- [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md)
-- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md)
-- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md)
-- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md)
-- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md)
-- [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)
-- [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md)
- [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md)
-- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md)
- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md)
-- [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/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)
+- [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)
+- [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/index.html.md)
- [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md)
-- [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md)
- [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/index.html.md)
-- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
-- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
-- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md)
-- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md)
-- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md)
-- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md)
- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md)
+- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/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)
+- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/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)
+- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md)
- [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md)
- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md)
- [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md)
- [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md)
- [associateLocationsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateLocationsWithSalesChannelsStep/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)
- [updateSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateSalesChannelsStep/index.html.md)
- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md)
+- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md)
+- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md)
+- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md)
+- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md)
+- [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)
- [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)
-- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md)
- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md)
+- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/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)
+- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/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)
-- [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)
- [createTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRegionsStep/index.html.md)
+- [createTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRateRulesStep/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)
-- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md)
- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md)
- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md)
-- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md)
-- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md)
- [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/index.html.md)
- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md)
+- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md)
+- [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/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)
+- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md)
+- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md)
+- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/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)
# Medusa CLI Reference
@@ -26391,166 +26427,6 @@ npx medusa develop
|\`-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.
-
-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 plugin:publish
-```
-
-***
-
-## 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|
-|---|---|---|---|---|
-|\`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
-```
-
-
-# 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 Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
-# 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\`|
-
-
-# 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.
-
-```bash
-npx medusa user --email [--password ]
-```
-
-## Options
-
-|Option|Description|Required|Default|
-|---|---|---|---|---|---|---|
-|\`-e \\`|The user's email.|Yes|-|
-|\`-p \\`|The user's password.|No|-|
-|\`-i \\`|The user's ID.|No|An automatically generated ID.|
-|\`--invite\`|Whether to create an invite instead of a user. When using this option, you don't need to specify a password.
-If ran successfully, you'll receive the invite token in the output.|No|\`false\`|
-
-
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -26687,6 +26563,166 @@ npx medusa exec [file] [args...]
|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+# new Command - Medusa CLI Reference
+
+Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project.
+
+```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.|
+
+
+# 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.
+
+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 plugin:publish
+```
+
+***
+
+## 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|
+|---|---|---|---|---|
+|\`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 Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
+# 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.|
+
+
+# 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\`|
+
+
+# user Command - Medusa CLI Reference
+
+Create a new admin user.
+
+```bash
+npx medusa user --email [--password ]
+```
+
+## Options
+
+|Option|Description|Required|Default|
+|---|---|---|---|---|---|---|
+|\`-e \\`|The user's email.|Yes|-|
+|\`-p \\`|The user's password.|No|-|
+|\`-i \\`|The user's ID.|No|An automatically generated ID.|
+|\`--invite\`|Whether to create an invite instead of a user. When using this option, you don't need to specify a password.
+If ran successfully, you'll receive the invite token in the output.|No|\`false\`|
+
+
# Medusa CLI Reference
The Medusa CLI tool provides commands that facilitate your development.
@@ -26772,6 +26808,22 @@ By default, the Medusa Admin is built to the `.medusa/server/public/admin` direc
If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead.
+# exec Command - Medusa CLI Reference
+
+Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md).
+
+```bash
+npx medusa exec [file] [args...]
+```
+
+## Arguments
+
+|Argument|Description|Required|
+|---|---|---|---|---|
+|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes|
+|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No|
+
+
# develop Command - Medusa CLI Reference
Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application.
@@ -26878,38 +26930,6 @@ npx medusa plugin:build
```
-# 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|
-
-
-# start Command - Medusa CLI Reference
-
-Start the Medusa application in production.
-
-```bash
-npx medusa start
-```
-
-## Options
-
-|Option|Description|Default|
-|---|---|---|---|---|
-|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
-|\`-p \\`|Set port of the Medusa server.|\`9000\`|
-
-
# 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).
@@ -26929,6 +26949,22 @@ npx medusa start-cluster
|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+# start Command - Medusa CLI Reference
+
+Start the Medusa application in production.
+
+```bash
+npx medusa start
+```
+
+## Options
+
+|Option|Description|Default|
+|---|---|---|---|---|
+|\`-H \\`|Set host of the Medusa server.|\`localhost\`|
+|\`-p \\`|Set port of the Medusa server.|\`9000\`|
+
+
# db Commands - Medusa CLI Reference
Commands starting with `db:` perform actions on the database.
@@ -27049,22 +27085,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.|
-# 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.
@@ -27084,6 +27104,22 @@ npx medusa user --email [--password ]
If ran successfully, you'll receive the invite token in the output.|No|\`false\`|
+# 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.|
+
+
# Medusa JS SDK
In this documentation, you'll learn how to install and use Medusa's JS SDK.
@@ -27384,327 +27420,327 @@ The object or class passed to `auth.storage` configuration must have the followi
## JS SDK Admin
-- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/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)
-- [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)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/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)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
-- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/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)
-- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md)
-- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md)
-- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md)
-- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md)
-- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md)
-- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md)
-- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.create/index.html.md)
-- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
-- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/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)
-- [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)
-- [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)
-- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md)
-- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
-- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
- [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)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/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)
-- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md)
-- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md)
+- [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/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)
+- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/index.html.md)
+- [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md)
+- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/index.html.md)
+- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md)
+- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancel/index.html.md)
+- [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)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md)
+- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md)
+- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md)
+- [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md)
+- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md)
+- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md)
+- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md)
+- [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/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)
+- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md)
+- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md)
+- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md)
+- [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)
+- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md)
+- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md)
+- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md)
+- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md)
+- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md)
+- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md)
+- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md)
+- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md)
+- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md)
+- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.removeItem/index.html.md)
+- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md)
- [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)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md)
-- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/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)
- [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md)
-- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/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/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md)
- [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)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/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)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md)
+- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md)
- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md)
- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md)
- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md)
+- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/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)
-- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md)
+- [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)
-- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md)
+- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md)
- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md)
-- [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md)
+- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/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)
+- [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)
- [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/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)
- [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)
-- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
-- [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)
- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md)
-- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md)
-- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md)
+- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md)
- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.retrieve/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md)
-- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/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)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/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/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)
- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md)
+- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md)
+- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/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)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md)
+- [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md)
- [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)
-- [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
-- [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)
-- [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)
-- [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)
- [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)
-- [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md)
- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md)
+- [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md)
-- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md)
-- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md)
-- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
-- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
-- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md)
- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
-- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md)
+- [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md)
+- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md)
+- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md)
+- [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/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)
+- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
+- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md)
+- [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)
+- [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)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md)
+- [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/index.html.md)
+- [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md)
+- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/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)
- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/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/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md)
-- [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)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/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)
+- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md)
+- [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)
- [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/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)
-- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md)
- [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md)
+- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md)
- [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md)
- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/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)
+- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md)
+- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md)
- [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md)
-- [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md)
- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md)
- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md)
- [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md)
- [updateVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateVariant/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/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/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
+- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md)
- [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/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md)
- [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/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)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
-- [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md)
-- [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md)
-- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md)
-- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md)
-- [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md)
-- [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/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)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
-- [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)
-- [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/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md)
- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/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)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
-- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
-- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/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/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md)
+- [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/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/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md)
- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md)
- [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md)
- [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md)
-- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md)
- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/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/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/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)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md)
+- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/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)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/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)
+- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/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)
+- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
+- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md)
+- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
+- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md)
+- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
+- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
+- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md)
+- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md)
+- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md)
+- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
+- [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)
+- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
+- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md)
+- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md)
+- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md)
- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/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)
- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md)
- [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md)
-- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/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/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)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/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)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/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)
-- [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)
+- [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)
-- [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)
- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.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/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)
-- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md)
-- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md)
-- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md)
-- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md)
-- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md)
-- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
-- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md)
-- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md)
-- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md)
-- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md)
-- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md)
-- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md)
-- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/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)
-- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/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)
-- [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)
-- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md)
-- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md)
-- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md)
-- [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md)
-- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/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/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)
- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md)
-- [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md)
+- [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/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)
- [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)
+- [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/User/methods/js_sdk.admin.User.delete/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md)
+- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md)
+- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md)
+- [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/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md)
+- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md)
+- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md)
+- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md)
+- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/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
-- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md)
-- [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)
-- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md)
- [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md)
+- [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md)
+- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md)
+- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md)
- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/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)
## JS SDK Store
- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md)
+- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md)
- [cart](https://docs.medusajs.com/references/js-sdk/store/cart/index.html.md)
- [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md)
- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md)
-- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md)
- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md)
-- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md)
- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md)
- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md)
+- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md)
# Configure Medusa Backend
@@ -28656,6 +28692,845 @@ 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.
+# Header - Admin Components
+
+Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action.
+
+
+
+To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content:
+
+```tsx title="src/admin/components/header.tsx"
+import { Heading, Button, Text } from "@medusajs/ui"
+import React from "react"
+import { Link, LinkProps } from "react-router-dom"
+import { ActionMenu, ActionMenuProps } from "./action-menu"
+
+export type HeadingProps = {
+ title: string
+ subtitle?: string
+ actions?: (
+ {
+ type: "button",
+ props: React.ComponentProps
+ link?: LinkProps
+ } |
+ {
+ type: "action-menu"
+ props: ActionMenuProps
+ } |
+ {
+ type: "custom"
+ children: React.ReactNode
+ }
+ )[]
+}
+
+export const Header = ({
+ title,
+ subtitle,
+ actions = [],
+}: HeadingProps) => {
+ return (
+
+ )
+}
+```
+
+The `Header` component shows a title, and optionally a subtitle and action buttons.
+
+The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component.
+
+It accepts the following props:
+
+- title: (\`string\`) The section's title.
+- subtitle: (\`string\`) The section's subtitle.
+- actions: (\`object\[]\`) An array of actions to show.
+
+ - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add.
+
+ \- If its value is \`button\`, it'll show a button that can have a link or an on-click action.
+
+ \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions.
+
+ \- If its value is \`custom\`, you can pass any React nodes to render.
+
+ - props: (object)
+
+ - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions.
+
+***
+
+## Example
+
+Use the `Header` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { Container } from "../components/container"
+import { Header } from "../components/header"
+
+const ProductWidget = () => {
+ return (
+
+ {
+ alert("You clicked the button.")
+ },
+ },
+ },
+ ]}
+ />
+
+ )
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component.
+
+
+# JSON View - Admin Components
+
+Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
+
+
+
+To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
+
+```tsx title="src/admin/components/json-view-section.tsx"
+import {
+ ArrowUpRightOnBox,
+ Check,
+ SquareTwoStack,
+ TriangleDownMini,
+ XMarkMini,
+} from "@medusajs/icons"
+import {
+ Badge,
+ Container,
+ Drawer,
+ Heading,
+ IconButton,
+ Kbd,
+} from "@medusajs/ui"
+import Primitive from "@uiw/react-json-view"
+import { CSSProperties, MouseEvent, Suspense, useState } from "react"
+
+type JsonViewSectionProps = {
+ data: object
+ title?: string
+}
+
+export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
+ const numberOfKeys = Object.keys(data).length
+
+ return (
+
+
+ JSON
+
+ {numberOfKeys} keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {numberOfKeys}
+
+
+
+
+
+
+ esc
+
+
+
+
+
+
+
+
+
+
+
}
+ >
+
+ } />
+ (
+ null
+ )}
+ />
+ (
+ undefined
+ )}
+ />
+ {
+ return (
+
+ {Object.keys(value as object).length} items
+
+ )
+ }}
+ />
+
+
+
+
+ :
+
+ {
+ return
+ }}
+ />
+
+
+
+
+
+
+
+ )
+}
+
+type CopiedProps = {
+ style?: CSSProperties
+ value: object | undefined
+}
+
+const Copied = ({ style, value }: CopiedProps) => {
+ const [copied, setCopied] = useState(false)
+
+ const handler = (e: MouseEvent) => {
+ e.stopPropagation()
+ setCopied(true)
+
+ if (typeof value === "string") {
+ navigator.clipboard.writeText(value)
+ } else {
+ const json = JSON.stringify(value, null, 2)
+ navigator.clipboard.writeText(json)
+ }
+
+ setTimeout(() => {
+ setCopied(false)
+ }, 2000)
+ }
+
+ const styl = { whiteSpace: "nowrap", width: "20px" }
+
+ if (copied) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
+```
+
+The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
+
+The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
+
+***
+
+## Example
+
+Use the `JsonViewSection` component in any widget or UI route.
+
+For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
+
+```tsx title="src/admin/widgets/product-widget.tsx"
+import { defineWidgetConfig } from "@medusajs/admin-sdk"
+import { JsonViewSection } from "../components/json-view-section"
+
+const ProductWidget = () => {
+ return
+}
+
+export const config = defineWidgetConfig({
+ zone: "product.details.before",
+})
+
+export default ProductWidget
+```
+
+This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
+
+
+# Data Table - Admin Components
+
+This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0).
+
+The [DataTable component in Medusa UI](https://docs.medusajs.com/ui/components/data-table/index.html.md) allows you to display data in a table with sorting, filtering, and pagination.
+
+You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application.
+
+Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages.
+
+## Example: DataTable with Data Fetching
+
+In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting.
+
+Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ createDataTableColumnHelper,
+} from "@medusajs/ui"
+import {
+ HttpTypes,
+} from "@medusajs/framework/types"
+
+const columnHelper = createDataTableColumnHelper()
+
+const columns = [
+ columnHelper.accessor("title", {
+ header: "Title",
+ // Enables sorting for the column.
+ enableSorting: true,
+ // If omitted, the header will be used instead if it's a string,
+ // otherwise the accessor key (id) will be used.
+ sortLabel: "Title",
+ // If omitted the default value will be "A-Z"
+ sortAscLabel: "A-Z",
+ // If omitted the default value will be "Z-A"
+ sortDescLabel: "Z-A",
+ }),
+ columnHelper.accessor("status", {
+ header: "Status",
+ cell: ({ getValue }) => {
+ const status = getValue()
+ return (
+
+ {status === "published" ? "Published" : "Draft"}
+
+ )
+ },
+ }),
+]
+```
+
+`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters:
+
+1. The column's key in the table's data.
+2. An object with the following properties:
+ - `header`: The column's header.
+ - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell.
+ - `enableSorting`: (optional) A boolean that enables sorting data by this column.
+ - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used.
+ - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z".
+ - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A".
+
+Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.
+
+To define the filters, add the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ createDataTableFilterHelper,
+} from "@medusajs/ui"
+
+const filterHelper = createDataTableFilterHelper()
+
+const filters = [
+ filterHelper.accessor("status", {
+ type: "select",
+ label: "Status",
+ options: [
+ {
+ label: "Published",
+ value: "published",
+ },
+ {
+ label: "Draft",
+ value: "draft",
+ },
+ ],
+ }),
+]
+```
+
+`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters:
+
+1. The key of a column in the table's data.
+2. An object with the following properties:
+ - `type`: The type of filter. It can be either:
+ - `select`: A select dropdown allowing users to choose multiple values.
+ - `radio`: A radio button allowing users to choose one value.
+ - `date`: A date picker allowing users to choose a date.
+ - `label`: The filter's label.
+ - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by.
+
+You'll now start creating the UI widget's component. Start by adding the necessary state variables:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import {
+ // ...
+ DataTablePaginationState,
+ DataTableFilteringState,
+ DataTableSortingState,
+} from "@medusajs/ui"
+import { useMemo, useState } from "react"
+
+// ...
+
+const limit = 15
+
+const CustomPage = () => {
+ const [pagination, setPagination] = useState({
+ pageSize: limit,
+ pageIndex: 0,
+ })
+ const [search, setSearch] = useState("")
+ const [filtering, setFiltering] = useState({})
+ const [sorting, setSorting] = useState(null)
+
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
+
+ // TODO add data fetching logic
+}
+```
+
+In the component, you've added the following state variables:
+
+- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties:
+ - `pageSize`: The number of items to show per page.
+ - `pageIndex`: The current page index.
+- `search`: A string that holds the search query.
+- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state.
+- `sorting`: An object of type `DataTableSortingState` that holds the sorting state.
+
+You've also added two memoized variables:
+
+- `offset`: How many items to skip when fetching data based on the current page.
+- `statusFilters`: The selected status filters, if any.
+
+Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { sdk } from "../../lib/config"
+import { useQuery } from "@tanstack/react-query"
+```
+
+This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list({
+ limit,
+ offset,
+ q: search,
+ status: statusFilters,
+ order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
+ }),
+ queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
+})
+
+// TODO configure data table
+```
+
+You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method:
+
+- `limit`: The number of products to fetch per page.
+- `offset`: The number of products to skip based on the current page.
+- `q`: The search query, if set.
+- `status`: The status filters, if set.
+- `order`: The sorting order, if set.
+
+So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
+
+Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ useDataTable,
+} from "@medusajs/ui"
+```
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+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,
+ },
+})
+
+// TODO render component
+```
+
+The `useDataTable` hook accepts an object with the following properties:
+
+- `columns`: The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility.
+- `data`: The products fetched from the Medusa application.
+- `getRowId`: A function that returns the unique ID of a row.
+- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages.
+- `isLoading`: A boolean that indicates if the data is being fetched.
+- `pagination`: An object to configure pagination. It accepts with the following properties:
+ - `state`: The pagination React state variable.
+ - `onPaginationChange`: A function that updates the pagination state.
+- `search`: An object to configure searching. It accepts the following properties:
+ - `state`: The search query React state variable.
+ - `onSearchChange`: A function that updates the search query state.
+- `filtering`: An object to configure filtering. It accepts the following properties:
+ - `state`: The filtering React state variable.
+ - `onFilteringChange`: A function that updates the filtering state.
+- `filters`: The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility.
+- `sorting`: An object to configure sorting. It accepts the following properties:
+ - `state`: The sorting React state variable.
+ - `onSortingChange`: A function that updates the sorting state.
+
+Finally, you'll render the data table. But first, add the following imports at the top of the page:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import {
+ // ...
+ DataTable,
+} from "@medusajs/ui"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Container } from "../../components/container"
+```
+
+Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard.
+
+Then, replace the `TODO` in the component with the following:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+return (
+
+
+
+
+ Products
+
+
+
+
+
+
+
+
+
+
+
+)
+```
+
+You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table.
+
+Lastly, export the component and the UI widget's configuration at the end of the file:
+
+```tsx title="src/admin/routes/custom/page.tsx"
+// other imports...
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+
+// ...
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities.
+
+### Full Example Code
+
+```tsx title="src/admin/routes/custom/page.tsx"
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import {
+ Badge,
+ createDataTableColumnHelper,
+ createDataTableFilterHelper,
+ DataTable,
+ DataTableFilteringState,
+ DataTablePaginationState,
+ DataTableSortingState,
+ Heading,
+ useDataTable,
+} from "@medusajs/ui"
+import { useQuery } from "@tanstack/react-query"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { sdk } from "../../lib/config"
+import { useMemo, useState } from "react"
+import { Container } from "../../components/container"
+import { HttpTypes, ProductStatus } from "@medusajs/framework/types"
+
+const columnHelper = createDataTableColumnHelper()
+
+const columns = [
+ columnHelper.accessor("title", {
+ header: "Title",
+ // Enables sorting for the column.
+ enableSorting: true,
+ // If omitted, the header will be used instead if it's a string,
+ // otherwise the accessor key (id) will be used.
+ sortLabel: "Title",
+ // If omitted the default value will be "A-Z"
+ sortAscLabel: "A-Z",
+ // If omitted the default value will be "Z-A"
+ sortDescLabel: "Z-A",
+ }),
+ columnHelper.accessor("status", {
+ header: "Status",
+ cell: ({ getValue }) => {
+ const status = getValue()
+ return (
+
+ {status === "published" ? "Published" : "Draft"}
+
+ )
+ },
+ }),
+]
+
+const filterHelper = createDataTableFilterHelper()
+
+const filters = [
+ filterHelper.accessor("status", {
+ type: "select",
+ label: "Status",
+ options: [
+ {
+ label: "Published",
+ value: "published",
+ },
+ {
+ label: "Draft",
+ value: "draft",
+ },
+ ],
+ }),
+]
+
+const limit = 15
+
+const CustomPage = () => {
+ const [pagination, setPagination] = useState({
+ pageSize: limit,
+ pageIndex: 0,
+ })
+ const [search, setSearch] = useState("")
+ const [filtering, setFiltering] = useState({})
+ const [sorting, setSorting] = useState(null)
+
+ const offset = useMemo(() => {
+ return pagination.pageIndex * limit
+ }, [pagination])
+ const statusFilters = useMemo(() => {
+ return (filtering.status || []) as ProductStatus
+ }, [filtering])
+
+ const { data, isLoading } = useQuery({
+ queryFn: () => sdk.admin.product.list({
+ limit,
+ offset,
+ q: search,
+ status: statusFilters,
+ order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
+ }),
+ queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
+ })
+
+ const table = useDataTable({
+ columns,
+ data: data?.products || [],
+ getRowId: (row) => row.id,
+ rowCount: data?.count || 0,
+ isLoading,
+ pagination: {
+ state: pagination,
+ onPaginationChange: setPagination,
+ },
+ search: {
+ state: search,
+ onSearchChange: setSearch,
+ },
+ filtering: {
+ state: filtering,
+ onFilteringChange: setFiltering,
+ },
+ filters,
+ sorting: {
+ // Pass the pagination state and updater to the table instance
+ state: sorting,
+ onSortingChange: setSorting,
+ },
+ })
+
+ return (
+
+
+
+
+ Products
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+
# Forms - Admin Components
The Medusa Admin has two types of forms:
@@ -29229,677 +30104,6 @@ This component uses the [Container](https://docs.medusajs.com/Users/shahednasser
It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form.
-# Single Column Layout - Admin Components
-
-The Medusa Admin has pages with a single column of content.
-
-This doesn't include the sidebar, only the main content.
-
-
-
-To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
-
-```tsx title="src/admin/layouts/single-column.tsx"
-export type SingleColumnLayoutProps = {
- children: React.ReactNode
-}
-
-export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
- return (
-
- {children}
-
- )
-}
-```
-
-The `SingleColumnLayout` accepts the content in the `children` props.
-
-***
-
-## Example
-
-Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Header } from "../../components/header"
-
-const CustomPage = () => {
- return (
-
-
-
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
-
-
-# Header - Admin Components
-
-Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action.
-
-
-
-To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content:
-
-```tsx title="src/admin/components/header.tsx"
-import { Heading, Button, Text } from "@medusajs/ui"
-import React from "react"
-import { Link, LinkProps } from "react-router-dom"
-import { ActionMenu, ActionMenuProps } from "./action-menu"
-
-export type HeadingProps = {
- title: string
- subtitle?: string
- actions?: (
- {
- type: "button",
- props: React.ComponentProps
- link?: LinkProps
- } |
- {
- type: "action-menu"
- props: ActionMenuProps
- } |
- {
- type: "custom"
- children: React.ReactNode
- }
- )[]
-}
-
-export const Header = ({
- title,
- subtitle,
- actions = [],
-}: HeadingProps) => {
- return (
-
- )
-}
-```
-
-The `Header` component shows a title, and optionally a subtitle and action buttons.
-
-The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component.
-
-It accepts the following props:
-
-- title: (\`string\`) The section's title.
-- subtitle: (\`string\`) The section's subtitle.
-- actions: (\`object\[]\`) An array of actions to show.
-
- - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add.
-
- \- If its value is \`button\`, it'll show a button that can have a link or an on-click action.
-
- \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions.
-
- \- If its value is \`custom\`, you can pass any React nodes to render.
-
- - props: (object)
-
- - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions.
-
-***
-
-## Example
-
-Use the `Header` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { Container } from "../components/container"
-import { Header } from "../components/header"
-
-const ProductWidget = () => {
- return (
-
- {
- alert("You clicked the button.")
- },
- },
- },
- ]}
- />
-
- )
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component.
-
-
-# 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.
-
-You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application.
-
-Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages.
-
-## Example: DataTable with Data Fetching
-
-In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting.
-
-Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- createDataTableColumnHelper,
-} from "@medusajs/ui"
-import {
- HttpTypes,
-} from "@medusajs/framework/types"
-
-const columnHelper = createDataTableColumnHelper()
-
-const columns = [
- columnHelper.accessor("title", {
- header: "Title",
- // Enables sorting for the column.
- enableSorting: true,
- // If omitted, the header will be used instead if it's a string,
- // otherwise the accessor key (id) will be used.
- sortLabel: "Title",
- // If omitted the default value will be "A-Z"
- sortAscLabel: "A-Z",
- // If omitted the default value will be "Z-A"
- sortDescLabel: "Z-A",
- }),
- columnHelper.accessor("status", {
- header: "Status",
- cell: ({ getValue }) => {
- const status = getValue()
- return (
-
- {status === "published" ? "Published" : "Draft"}
-
- )
- },
- }),
-]
-```
-
-`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters:
-
-1. The column's key in the table's data.
-2. An object with the following properties:
- - `header`: The column's header.
- - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell.
- - `enableSorting`: (optional) A boolean that enables sorting data by this column.
- - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used.
- - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z".
- - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A".
-
-Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status.
-
-To define the filters, add the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import {
- // ...
- createDataTableFilterHelper,
-} from "@medusajs/ui"
-
-const filterHelper = createDataTableFilterHelper()
-
-const filters = [
- filterHelper.accessor("status", {
- type: "select",
- label: "Status",
- options: [
- {
- label: "Published",
- value: "published",
- },
- {
- label: "Draft",
- value: "draft",
- },
- ],
- }),
-]
-```
-
-`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters:
-
-1. The key of a column in the table's data.
-2. An object with the following properties:
- - `type`: The type of filter. It can be either:
- - `select`: A select dropdown allowing users to choose multiple values.
- - `radio`: A radio button allowing users to choose one value.
- - `date`: A date picker allowing users to choose a date.
- - `label`: The filter's label.
- - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by.
-
-You'll now start creating the UI widget's component. Start by adding the necessary state variables:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import {
- // ...
- DataTablePaginationState,
- DataTableFilteringState,
- DataTableSortingState,
-} from "@medusajs/ui"
-import { useMemo, useState } from "react"
-
-// ...
-
-const limit = 15
-
-const CustomPage = () => {
- const [pagination, setPagination] = useState({
- pageSize: limit,
- pageIndex: 0,
- })
- const [search, setSearch] = useState("")
- const [filtering, setFiltering] = useState({})
- const [sorting, setSorting] = useState(null)
-
- const offset = useMemo(() => {
- return pagination.pageIndex * limit
- }, [pagination])
- const statusFilters = useMemo(() => {
- return (filtering.status || []) as ProductStatus
- }, [filtering])
-
- // TODO add data fetching logic
-}
-```
-
-In the component, you've added the following state variables:
-
-- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties:
- - `pageSize`: The number of items to show per page.
- - `pageIndex`: The current page index.
-- `search`: A string that holds the search query.
-- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state.
-- `sorting`: An object of type `DataTableSortingState` that holds the sorting state.
-
-You've also added two memoized variables:
-
-- `offset`: How many items to skip when fetching data based on the current page.
-- `statusFilters`: The selected status filters, if any.
-
-Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { sdk } from "../../lib/config"
-import { useQuery } from "@tanstack/react-query"
-```
-
-This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest).
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list({
- limit,
- offset,
- q: search,
- status: statusFilters,
- order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
- }),
- queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
-})
-
-// TODO configure data table
-```
-
-You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method:
-
-- `limit`: The number of products to fetch per page.
-- `offset`: The number of products to skip based on the current page.
-- `q`: The search query, if set.
-- `status`: The status filters, if set.
-- `order`: The sorting order, if set.
-
-So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters.
-
-Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- useDataTable,
-} from "@medusajs/ui"
-```
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-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,
- },
-})
-
-// TODO render component
-```
-
-The `useDataTable` hook accepts an object with the following properties:
-
-- `columns`: The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility.
-- `data`: The products fetched from the Medusa application.
-- `getRowId`: A function that returns the unique ID of a row.
-- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages.
-- `isLoading`: A boolean that indicates if the data is being fetched.
-- `pagination`: An object to configure pagination. It accepts with the following properties:
- - `state`: The pagination React state variable.
- - `onPaginationChange`: A function that updates the pagination state.
-- `search`: An object to configure searching. It accepts the following properties:
- - `state`: The search query React state variable.
- - `onSearchChange`: A function that updates the search query state.
-- `filtering`: An object to configure filtering. It accepts the following properties:
- - `state`: The filtering React state variable.
- - `onFilteringChange`: A function that updates the filtering state.
-- `filters`: The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility.
-- `sorting`: An object to configure sorting. It accepts the following properties:
- - `state`: The sorting React state variable.
- - `onSortingChange`: A function that updates the sorting state.
-
-Finally, you'll render the data table. But first, add the following imports at the top of the page:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import {
- // ...
- DataTable,
-} from "@medusajs/ui"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { Container } from "../../components/container"
-```
-
-Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard.
-
-Then, replace the `TODO` in the component with the following:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-return (
-
-
-
-
- Products
-
-
-
-
-
-
-
-
-
-
-
-)
-```
-
-You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table.
-
-Lastly, export the component and the UI widget's configuration at the end of the file:
-
-```tsx title="src/admin/routes/custom/page.tsx"
-// other imports...
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-
-// ...
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities.
-
-### Full Example Code
-
-```tsx title="src/admin/routes/custom/page.tsx"
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import {
- Badge,
- createDataTableColumnHelper,
- createDataTableFilterHelper,
- DataTable,
- DataTableFilteringState,
- DataTablePaginationState,
- DataTableSortingState,
- Heading,
- useDataTable,
-} from "@medusajs/ui"
-import { useQuery } from "@tanstack/react-query"
-import { SingleColumnLayout } from "../../layouts/single-column"
-import { sdk } from "../../lib/config"
-import { useMemo, useState } from "react"
-import { Container } from "../../components/container"
-import { HttpTypes, ProductStatus } from "@medusajs/framework/types"
-
-const columnHelper = createDataTableColumnHelper()
-
-const columns = [
- columnHelper.accessor("title", {
- header: "Title",
- // Enables sorting for the column.
- enableSorting: true,
- // If omitted, the header will be used instead if it's a string,
- // otherwise the accessor key (id) will be used.
- sortLabel: "Title",
- // If omitted the default value will be "A-Z"
- sortAscLabel: "A-Z",
- // If omitted the default value will be "Z-A"
- sortDescLabel: "Z-A",
- }),
- columnHelper.accessor("status", {
- header: "Status",
- cell: ({ getValue }) => {
- const status = getValue()
- return (
-
- {status === "published" ? "Published" : "Draft"}
-
- )
- },
- }),
-]
-
-const filterHelper = createDataTableFilterHelper()
-
-const filters = [
- filterHelper.accessor("status", {
- type: "select",
- label: "Status",
- options: [
- {
- label: "Published",
- value: "published",
- },
- {
- label: "Draft",
- value: "draft",
- },
- ],
- }),
-]
-
-const limit = 15
-
-const CustomPage = () => {
- const [pagination, setPagination] = useState({
- pageSize: limit,
- pageIndex: 0,
- })
- const [search, setSearch] = useState("")
- const [filtering, setFiltering] = useState({})
- const [sorting, setSorting] = useState(null)
-
- const offset = useMemo(() => {
- return pagination.pageIndex * limit
- }, [pagination])
- const statusFilters = useMemo(() => {
- return (filtering.status || []) as ProductStatus
- }, [filtering])
-
- const { data, isLoading } = useQuery({
- queryFn: () => sdk.admin.product.list({
- limit,
- offset,
- q: search,
- status: statusFilters,
- order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined,
- }),
- queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]],
- })
-
- const table = useDataTable({
- columns,
- data: data?.products || [],
- getRowId: (row) => row.id,
- rowCount: data?.count || 0,
- isLoading,
- pagination: {
- state: pagination,
- onPaginationChange: setPagination,
- },
- search: {
- state: search,
- onSearchChange: setSearch,
- },
- filtering: {
- state: filtering,
- onFilteringChange: setFiltering,
- },
- filters,
- sorting: {
- // Pass the pagination state and updater to the table instance
- state: sorting,
- onSortingChange: setSorting,
- },
- })
-
- return (
-
-
-
-
- Products
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-
# Section Row - Admin Components
The Medusa Admin often shows information in rows of label-values, such as when showing a product's details.
@@ -29992,313 +30196,6 @@ export default ProductWidget
This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component.
-# Two Column Layout - Admin Components
-
-The Medusa Admin has pages with two columns of content.
-
-This doesn't include the sidebar, only the main content.
-
-
-
-To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
-
-```tsx title="src/admin/layouts/two-column.tsx"
-export type TwoColumnLayoutProps = {
- firstCol: React.ReactNode
- secondCol: React.ReactNode
-}
-
-export const TwoColumnLayout = ({
- firstCol,
- secondCol,
-}: TwoColumnLayoutProps) => {
- return (
-
-
- {firstCol}
-
-
- {secondCol}
-
-
- )
-}
-```
-
-The `TwoColumnLayout` accepts two props:
-
-- `firstCol` indicating the content of the first column.
-- `secondCol` indicating the content of the second column.
-
-***
-
-## Example
-
-Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
-
-```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
-import { defineRouteConfig } from "@medusajs/admin-sdk"
-import { ChatBubbleLeftRight } from "@medusajs/icons"
-import { Container } from "../../components/container"
-import { Header } from "../../components/header"
-import { TwoColumnLayout } from "../../layouts/two-column"
-
-const CustomPage = () => {
- return (
-
-
-
- }
- secondCol={
-
-
-
- }
- />
- )
-}
-
-export const config = defineRouteConfig({
- label: "Custom",
- icon: ChatBubbleLeftRight,
-})
-
-export default CustomPage
-```
-
-This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
-
-
-# JSON View - Admin Components
-
-Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format.
-
-
-
-To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content:
-
-```tsx title="src/admin/components/json-view-section.tsx"
-import {
- ArrowUpRightOnBox,
- Check,
- SquareTwoStack,
- TriangleDownMini,
- XMarkMini,
-} from "@medusajs/icons"
-import {
- Badge,
- Container,
- Drawer,
- Heading,
- IconButton,
- Kbd,
-} from "@medusajs/ui"
-import Primitive from "@uiw/react-json-view"
-import { CSSProperties, MouseEvent, Suspense, useState } from "react"
-
-type JsonViewSectionProps = {
- data: object
- title?: string
-}
-
-export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
- const numberOfKeys = Object.keys(data).length
-
- return (
-
-
- JSON
-
- {numberOfKeys} keys
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {numberOfKeys}
-
-
-
-
-
-
- esc
-
-
-
-
-
-
-
-
-
-
-
}
- >
-
- } />
- (
- null
- )}
- />
- (
- undefined
- )}
- />
- {
- return (
-
- {Object.keys(value as object).length} items
-
- )
- }}
- />
-
-
-
-
- :
-
- {
- return
- }}
- />
-
-
-
-
-
-
-
- )
-}
-
-type CopiedProps = {
- style?: CSSProperties
- value: object | undefined
-}
-
-const Copied = ({ style, value }: CopiedProps) => {
- const [copied, setCopied] = useState(false)
-
- const handler = (e: MouseEvent) => {
- e.stopPropagation()
- setCopied(true)
-
- if (typeof value === "string") {
- navigator.clipboard.writeText(value)
- } else {
- const json = JSON.stringify(value, null, 2)
- navigator.clipboard.writeText(json)
- }
-
- setTimeout(() => {
- setCopied(false)
- }, 2000)
- }
-
- const styl = { whiteSpace: "nowrap", width: "20px" }
-
- if (copied) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
- )
-}
-```
-
-The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window.
-
-The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer.
-
-***
-
-## Example
-
-Use the `JsonViewSection` component in any widget or UI route.
-
-For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content:
-
-```tsx title="src/admin/widgets/product-widget.tsx"
-import { defineWidgetConfig } from "@medusajs/admin-sdk"
-import { JsonViewSection } from "../components/json-view-section"
-
-const ProductWidget = () => {
- return
-}
-
-export const config = defineWidgetConfig({
- zone: "product.details.before",
-})
-
-export default ProductWidget
-```
-
-This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`.
-
-
# 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.
@@ -30591,6 +30488,145 @@ If `data` isn't `undefined`, you display the `Table` component passing it the fo
To test it out, log into the Medusa Admin and open `http://localhost:9000/app/custom`. You'll find a table of products with pagination.
+# Single Column Layout - Admin Components
+
+The Medusa Admin has pages with a single column of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/single-column.tsx"
+export type SingleColumnLayoutProps = {
+ children: React.ReactNode
+}
+
+export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+The `SingleColumnLayout` accepts the content in the `children` props.
+
+***
+
+## Example
+
+Use the `SingleColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { SingleColumnLayout } from "../../layouts/single-column"
+import { Header } from "../../components/header"
+
+const CustomPage = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components.
+
+
+# Two Column Layout - Admin Components
+
+The Medusa Admin has pages with two columns of content.
+
+This doesn't include the sidebar, only the main content.
+
+
+
+To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content:
+
+```tsx title="src/admin/layouts/two-column.tsx"
+export type TwoColumnLayoutProps = {
+ firstCol: React.ReactNode
+ secondCol: React.ReactNode
+}
+
+export const TwoColumnLayout = ({
+ firstCol,
+ secondCol,
+}: TwoColumnLayoutProps) => {
+ return (
+
+
+ {firstCol}
+
+
+ {secondCol}
+
+
+ )
+}
+```
+
+The `TwoColumnLayout` accepts two props:
+
+- `firstCol` indicating the content of the first column.
+- `secondCol` indicating the content of the second column.
+
+***
+
+## Example
+
+Use the `TwoColumnLayout` component in your UI routes that have a single column. For example:
+
+```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]}
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import { ChatBubbleLeftRight } from "@medusajs/icons"
+import { Container } from "../../components/container"
+import { Header } from "../../components/header"
+import { TwoColumnLayout } from "../../layouts/two-column"
+
+const CustomPage = () => {
+ return (
+
+
+
+ }
+ secondCol={
+
+
+
+ }
+ />
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Custom",
+ icon: ChatBubbleLeftRight,
+})
+
+export default CustomPage
+```
+
+This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components.
+
+
# Service Factory Reference
This section of the documentation provides a reference of the methods generated for services extending the service factory (`MedusaService`), and how to use them.
@@ -30618,323 +30654,42 @@ Some examples of method names:
The reference uses only the operation name to refer to the method.
-# Filter Records - Service Factory Reference
+# create Method - 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 method creates one or more records of the data model.
-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
+## Create One Record
```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",
- ],
+const post = await postModuleService.createPosts({
+ name: "My Post",
+ published_at: new Date(),
+ metadata: {
+ external_id: "1234",
},
})
```
-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.
+If an object is passed of the method, an object of the created record is also returned.
***
-## Match Text Like Value
-
-This filter only applies to text-like properties, including `id` and `enum` properties.
+## Create Multiple Records
```ts
-const posts = await postModuleService.listPosts({
- name: {
- $like: "My%",
+const posts = await postModuleService.createPosts([
+ {
+ name: "My Post",
+ published_at: new Date(),
},
-})
-```
-
-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(),
+ {
+ name: "My Other Post",
+ published_at: 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.
-
-
-# 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).
-
-
-# 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.
+If an array is passed of the method, an array of the created records is also returned.
# list Method - Service Factory Reference
@@ -31055,6 +30810,133 @@ To sort records by one or more properties, pass to the second object parameter t
The method returns an array of the first `15` records matching the filters.
+# 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).
+
+
+# softDelete Method - Service Factory Reference
+
+This method soft deletes one or more records of the data model.
+
+## Soft Delete One Record
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts(
+ "123"
+)
+```
+
+### Parameters
+
+To soft delete a record, pass its ID as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+***
+
+## Soft Delete Multiple Records
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts([
+ "123",
+ "321",
+])
+```
+
+### Parameters
+
+To soft delete multiple records, pass an array of IDs as a parameter of the method.
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: [
+ "123",
+ "321",
+ ],
+}
+```
+
+***
+
+## Soft Delete Records Matching Filters
+
+```ts
+const deletedPosts = await postModuleService.softDeletePosts({
+ name: "My Post",
+})
+```
+
+### Parameters
+
+To soft delete records matching a set of filters, pass an object of filters as a parameter.
+
+Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md).
+
+### Returns
+
+The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+
+For example, the returned object of the above example is:
+
+```ts
+deletedPosts = {
+ post_id: ["123"],
+}
+```
+
+
# retrieve Method - Service Factory Reference
This method retrieves one record of the data model by its ID.
@@ -31199,129 +31081,140 @@ restoredPosts = {
```
-# softDelete Method - Service Factory Reference
+# listAndCount Method - Service Factory Reference
-This method soft deletes one or more records of the data model.
+This method retrieves a list of records with the total count.
-## Soft Delete One Record
+## Retrieve List of Records
```ts
-const deletedPosts = await postModuleService.softDeletePosts(
- "123"
-)
+const [posts, count] = await postModuleService.listAndCountPosts()
```
-### Parameters
+If no parameters are passed, the method returns an array with two items:
-To soft delete a record, pass its ID as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: ["123"],
-}
-```
+1. The first is an array of the first `15` records retrieved.
+2. The second is the total count of records.
***
-## Soft Delete Multiple Records
+## Filter Records
```ts
-const deletedPosts = await postModuleService.softDeletePosts([
- "123",
- "321",
-])
-```
-
-### Parameters
-
-To soft delete multiple records, pass an array of IDs as a parameter of the method.
-
-### Returns
-
-The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
-
-For example, the returned object of the above example is:
-
-```ts
-deletedPosts = {
- post_id: [
- "123",
- "321",
- ],
-}
-```
-
-***
-
-## Soft Delete Records Matching Filters
-
-```ts
-const deletedPosts = await postModuleService.softDeletePosts({
- name: "My Post",
+const [posts, count] = await postModuleService.listAndCountPosts({
+ id: ["123", "321"],
})
```
### Parameters
-To soft delete records matching a set of filters, pass an object of filters as a parameter.
+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 object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs.
+The method returns an array with two items:
-For example, the returned object of the above example is:
+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
-deletedPosts = {
- post_id: ["123"],
-}
+const [posts, count] = await postModuleService.listAndCountPosts({}, {
+ relations: ["author"],
+})
```
+### Parameters
-# create Method - Service Factory Reference
+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.
-This method creates one or more records of the data model.
+### Returns
-## Create One Record
+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 post = await postModuleService.createPosts({
- name: "My Post",
- published_at: new Date(),
- metadata: {
- external_id: "1234",
+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",
},
})
```
-If an object is passed of the method, an object of the created record is also returned.
+### 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:
-## Create Multiple Records
+- `ASC` to sort by this property in the ascending order.
+- `DESC` to sort by this property in the descending order.
-```ts
-const posts = await postModuleService.createPosts([
- {
- name: "My Post",
- published_at: new Date(),
- },
- {
- name: "My Other Post",
- published_at: new Date(),
- },
-])
-```
+### Returns
-If an array is passed of the method, an array of the created records is also returned.
+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.
# update Method - Service Factory Reference
@@ -31447,6 +31340,149 @@ 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?
@@ -31888,168 +31924,6 @@ 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
-```
-
-
-# clx
-
-Utility function for working with classNames.
-
-## Usage
-
-***
-
-The `clx` function is a utility function for working with classNames. It is built using [clsx](https://www.npmjs.com/package/clsx) and [tw-merge](https://www.npmjs.com/package/tw-merge) and is intended to be used with [Tailwind CSS](https://tailwindcss.com/).
-
-```tsx
-import { clx } from "@medusajs/ui"
-
-type BoxProps = {
- className?: string
- children: React.ReactNode
- mt: "sm" | "md" | "lg"
-}
-
-const Box = ({ className, children, mt }: BoxProps) => {
- return (
-
- {children}
-
- )
-}
-
-```
-
-In the above example the utility is used to apply a base style, a margin top that is dependent on the `mt` prop and a custom className.
-The Box component accepts a `className` prop that is merged with the other classNames, and the underlying usage of `tw-merge` ensures that all Tailwind CSS classes are merged without style conflicts.
-
-
# Alert
A component for displaying important messages.
@@ -38363,3 +38237,165 @@ If you're using the `Tooltip` component in a project other than the Medusa Admin
- delayDuration: (number) The duration from when the pointer enters the trigger until the tooltip gets opened. Default: 100
- skipDelayDuration: (number) How much time a user has to enter another trigger without incurring a delay again. Default: 300
- 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.
+
+## Usage
+
+***
+
+The `clx` function is a utility function for working with classNames. It is built using [clsx](https://www.npmjs.com/package/clsx) and [tw-merge](https://www.npmjs.com/package/tw-merge) and is intended to be used with [Tailwind CSS](https://tailwindcss.com/).
+
+```tsx
+import { clx } from "@medusajs/ui"
+
+type BoxProps = {
+ className?: string
+ children: React.ReactNode
+ mt: "sm" | "md" | "lg"
+}
+
+const Box = ({ className, children, mt }: BoxProps) => {
+ return (
+
+ {children}
+
+ )
+}
+
+```
+
+In the above example the utility is used to apply a base style, a margin top that is dependent on the `mt` prop and a custom className.
+The Box component accepts a `className` prop that is merged with the other classNames, and the underlying usage of `tw-merge` ensures that all Tailwind CSS classes are merged without style conflicts.