diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 20f27cc014..7d2369fb41 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -140,6 +140,70 @@ By the end of this chapter, you’ll learn: - 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. + +## Medusa Project Components + +A standard Medusa project is made up of: + +- Medusa application: The Medusa server and the Medusa Admin. +- One or more storefronts + +![Diagram showcasing the connection between the three deployed components](https://res.cloudinary.com/dza7lstvk/image/upload/v1708600807/Medusa%20Book/deployment-options_ceuuvo.jpg) + +You deploy the Medusa application, with the server and admin, separately from the storefront. + +*** + +## Deploying the Medusa Application + +You must deploy the Medusa application before the storefront, as it connects to the server and won’t work without a deployed Medusa server URL. + +The Medusa application must be deployed to a hosting provider supporting Node.js server deployments, such as Railway, DigitalOcean, AWS, Heroku, etc… + +![Diagram showcasing how the Medusa server and its associated services would be deployed](https://res.cloudinary.com/dza7lstvk/image/upload/v1708600972/Medusa%20Book/backend_deployment_pgexo3.jpg) + +Your server connects to a PostgreSQL database, Redis, and other services relevant for your setup. Most hosting providers support deploying and managing these databases along with your Medusa server (such as Railway and DigitalOcean). + +When you deploy your Medusa application, you also deploy the Medusa Admin. For optimal experience, your hosting provider and plan must offer at least 2GB of RAM. + +### How to Deploy Medusa? + +Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. + +With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: + +- Push to deploy. +- Multiple testing environments. +- Preview environments for new PRs. +- Test on production-like data. + +[Sign up and learn more about Medusa Cloud](https://medusajs.com/contact) + +To self-host Medusa, the [next chapter](https://docs.medusajs.com/learn/deployment/general/index.html.md) explains the general steps to deploy your Medusa application. Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for general and specific hosting providers. + +*** + +## Deploying the Storefront + +The storefront is deployed separately from the Medusa application, and the hosting options depend on the tools and frameworks you use to create the storefront. + +If you’re using the Next.js Starter storefront, you may deploy the storefront to any hosting provider that supports frontend frameworks, such as Vercel. + +Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the storefront on the free plan for development purposes, but for commercial projects, you must update your Vercel plan. + +Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers. + + +# More Resources + +The Development Resources documentation provides guides and references that are useful for your development. This documentation included links to parts of the Development Resources documentation where necessary. + +Check out the Development Resources documentation [here](https://docs.medusajs.com/resources/index.html.md). + + # Install Medusa In this chapter, you'll learn how to install and run a Medusa application. @@ -233,7 +297,7 @@ This directory is the central place for your custom development. It includes the - `admin`: Holds your admin dashboard's custom [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md) and [UI routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md). - `api`: Holds your custom [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) that are added as endpoints in your Medusa application. - `jobs`: Holds your [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) that run at a specified interval during your Medusa application's runtime. -- `links`: Holds you [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) that build associations between data models of different modules. +- `links`: Holds your [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) that build associations between data models of different modules. - `modules`: Holds your custom [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) that implement custom business logic. - `scripts`: Holds your custom [scripts](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts/index.html.md) to be executed using Medusa's CLI tool. - `subscribers`: Holds your [event listeners](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) that are executed asynchronously whenever an event is emitted. @@ -264,70 +328,6 @@ Refer to [this documentation](https://docs.medusajs.com/learn/update/index.html. In the next chapters, you'll learn about the architecture of your Medusa application, then learn how to customize your application to build custom features. -# More Resources - -The Development Resources documentation provides guides and references that are useful for your development. This documentation included links to parts of the Development Resources documentation where necessary. - -Check out the Development Resources documentation [here](https://docs.medusajs.com/resources/index.html.md). - - -# Medusa Deployment Overview - -In this chapter, you’ll learn the general approach to deploying the Medusa application. - -## Medusa Project Components - -A standard Medusa project is made up of: - -- Medusa application: The Medusa server and the Medusa Admin. -- One or more storefronts - -![Diagram showcasing the connection between the three deployed components](https://res.cloudinary.com/dza7lstvk/image/upload/v1708600807/Medusa%20Book/deployment-options_ceuuvo.jpg) - -You deploy the Medusa application, with the server and admin, separately from the storefront. - -*** - -## Deploying the Medusa Application - -You must deploy the Medusa application before the storefront, as it connects to the server and won’t work without a deployed Medusa server URL. - -The Medusa application must be deployed to a hosting provider supporting Node.js server deployments, such as Railway, DigitalOcean, AWS, Heroku, etc… - -![Diagram showcasing how the Medusa server and its associated services would be deployed](https://res.cloudinary.com/dza7lstvk/image/upload/v1708600972/Medusa%20Book/backend_deployment_pgexo3.jpg) - -Your server connects to a PostgreSQL database, Redis, and other services relevant for your setup. Most hosting providers support deploying and managing these databases along with your Medusa server (such as Railway and DigitalOcean). - -When you deploy your Medusa application, you also deploy the Medusa Admin. For optimal experience, your hosting provider and plan must offer at least 2GB of RAM. - -### How to Deploy Medusa? - -Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. - -With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: - -- Push to deploy. -- Multiple testing environments. -- Preview environments for new PRs. -- Test on production-like data. - -[Sign up and learn more about Medusa Cloud](https://medusajs.com/contact) - -To self-host Medusa, the [next chapter](https://docs.medusajs.com/learn/deployment/general/index.html.md) explains the general steps to deploy your Medusa application. Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for general and specific hosting providers. - -*** - -## Deploying the Storefront - -The storefront is deployed separately from the Medusa application, and the hosting options depend on the tools and frameworks you use to create the storefront. - -If you’re using the Next.js Starter storefront, you may deploy the storefront to any hosting provider that supports frontend frameworks, such as Vercel. - -Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan can only be used for personal, non-commercial projects. So, you can deploy the storefront on the free plan for development purposes, but for commercial projects, you must update your Vercel plan. - -Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers. - - # Storefront Development The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience. @@ -846,6 +846,309 @@ Medusa's Testing Framework works for integration tests only. You can write unit The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests. +# General Medusa Application Deployment Guide + +In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform. + +Find how-to guides for specific platforms in [this documentation](https://docs.medusajs.com/resources/deployment/index.html.md). + +Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Medusa Cloud](https://medusajs.com/contact) + +Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. + +With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: + +- Push to deploy. +- Multiple testing environments. +- Preview environments for new PRs. +- Test on production-like data. + +### Prerequisites + +- [Medusa application’s codebase hosted on GitHub repository.](https://docs.medusajs.com/learn/index.html.md) + +## Hosting Provider Requirements + +When you deploy your Medusa application, make sure your chosen hosting provider supports deploying the following resources: + +1. PostgreSQL database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the PostgreSQL database. +2. Redis database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the Redis database. +3. Medusa application in server and worker mode. This means your hosting provider should support deploying two applications or instances from the same codebase. +4. For optimal experience, the hosting provider and plan must offer at least 2GB of RAM. + +*** + +## 1. Configure Medusa Application + +### Worker Mode + +The `workerMode` configuration determines which mode the Medusa application runs in. + +When you deploy the Medusa application, you deploy two instances: one in server mode, and one in worker mode. + +Learn more about the `workerMode` configuration in [this document](https://docs.medusajs.com/resources/references/medusa-config#workermode/index.html.md). + +So, add the following configuration in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + projectConfig: { + // ... + workerMode: process.env.MEDUSA_WORKER_MODE as "shared" | "worker" | "server", + }, +}) +``` + +Later, you’ll set different values of the `MEDUSA_WORKER_MODE` environment variable for each Medusa application deployment or instance. + +### Configure Medusa Admin + +You need to disable the Medusa Admin in the worker Medusa application, while keeping it enabled in the server Medusa application. So, add the following configuration in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + admin: { + disable: process.env.DISABLE_MEDUSA_ADMIN === "true", + }, +}) +``` + +Later, you’ll set different values of the `DISABLE_MEDUSA_ADMIN` environment variable. + +### Configure Redis URL + +The `redisUrl` configuration specifies the connection URL to Redis to store the Medusa server's session. + +Learn more in the [Medusa Configuration documentation](https://docs.medusajs.com/resources/references/medusa-config#redisurl/index.html.md). + +So, add the following configuration in `medusa-config.ts` : + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + projectConfig: { + // ... + redisUrl: process.env.REDIS_URL, + }, +}) +``` + +*** + +## 2. Add predeploy Script + +Before you start the Medusa application in production, you should always run migrations and sync links. + +So, add the following script in `package.json`: + +```json +"scripts": { + // ... + "predeploy": "medusa db:migrate" +}, +``` + +*** + +## 3. Install Production Modules and Providers + +By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider. + +It’s highly recommended to instead use modules and providers suitable for production, including: + +- [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md) +- [Redis Event Bus Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) +- [Workflow Engine Redis Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) +- [S3 File Module Provider](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) (or other file module providers production-ready). +- [SendGrid Notification Module Provider](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) (or other notification module providers production-ready). + +Then, add these modules in `medusa-config.ts`: + +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/cache-redis", + options: { + redisUrl: process.env.REDIS_URL, + }, + }, + { + resolve: "@medusajs/medusa/event-bus-redis", + options: { + redisUrl: process.env.REDIS_URL, + }, + }, + { + resolve: "@medusajs/medusa/workflow-engine-redis", + options: { + redis: { + url: process.env.REDIS_URL, + }, + }, + }, + ], +}) +``` + +Check out the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) and [Architectural Modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) documentation for other modules and providers to use. + +*** + +## 4. Create PostgreSQL and Redis Databases + +Your Medusa application must connect to PostgreSQL and Redis databases. So, before you deploy it, create production PostgreSQL and Redis databases. + +If your hosting provider doesn't support databases, you can use [Neon for PostgreSQL database hosting](https://neon.tech/), and [Redis Cloud for the Redis database hosting](https://redis.io/cloud/). + +After hosting both databases, keep their connection URLs for the next steps. + +*** + +## 5. Deploy Medusa Application in Server Mode + +As mentioned earlier, you'll deploy two instances or create two deployments of your Medusa application: one in server mode, and the other in worker mode. + +The deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment. + +### Set Environment Variables + +When setting the environment variables of the Medusa application, set the following variables: + +```bash +COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET +JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET +STORE_CORS= # STOREFRONT URL +ADMIN_CORS= # ADMIN URL +AUTH_CORS= # STOREFRONT AND ADMIN URLS, SEPARATED BY COMMAS +DISABLE_MEDUSA_ADMIN=false +MEDUSA_WORKER_MODE=server +PORT=9000 +DATABASE_URL # POSTGRES DATABASE URL +REDIS_URL= # REDIS DATABASE URL +``` + +Where: + +- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret. +- `STORE_CORS`'s value is the URL of your storefront. If you don’t have it yet, you can skip adding it for now. +- `ADMIN_CORS`'s value is the URL of the admin dashboard, which is the same as the server Medusa application. You can add it later if you don't currently have it. +- `AUTH_CORS`'s value is the URLs of any application authenticating users, customers, or other actor types, such as the storefront and admin URLs. The URLs are separated by commas. If you don’t have the URLs yet, you can set its value later. +- Set `DISABLE_MEDUSA_ADMIN`'s value to `false` so that the admin is built with the server application. +- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL` +- Set the Redis database's connection URL as the value of `REDIS_URL`. + +Feel free to add any other relevant environment variables, such as for integrations and architectural modules. + +### Set Start Command + +The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. + +So, you must run the `start` command from the `.medusa/server` directory. + +If your hosting provider doesn't support setting a current-working directory, set the start command to the following: + +```bash npm2yarn +cd .medusa/server && npm run predeploy && npm run start +``` + +### Set Backend URL in Admin Configuration + +After you’ve obtained the Medusa application’s URL, add the following configuration to `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + admin: { + // ... + backendUrl: process.env.MEDUSA_BACKEND_URL, + }, +}) +``` + +Then, push the changes to the GitHub repository or deployed application. + +In your hosting provider, add or modify the following environment variables for the Medusa application in server mode: + +```bash +ADMIN_CORS= # MEDUSA APPLICATION URL +AUTH_CORS= # ADD MEDUSA APPLICATION URL +MEDUSA_BACKEND_URL= # URL TO DEPLOYED MEDUSA APPLICATION +``` + +Where you set the value of `ADMIN_CORS` and `MEDUSA_BACKEND_URL` to the Medusa application’s URL, and you add the URL to `AUTH_CORS`. + +Remember to separate URLs in `AUTH_CORS` by commas. + +*** + +## 6. Deploy Medusa Application in Worker Mode + +Next, you'll deploy the Medusa application in worker mode. + +As explained in the previous section, the deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment. + +### Set Environment Variables + +When setting the environment variables of the Medusa application, set the following variables: + +```bash +COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET +JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET +DISABLE_MEDUSA_ADMIN=true +MEDUSA_WORKER_MODE=worker +PORT=9000 +DATABASE_URL # POSTGRES DATABASE URL +REDIS_URL= # REDIS DATABASE URL +``` + +Where: + +- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret. +- Set `DISABLE_MEDUSA_ADMIN`'s value to `true` so that the admin isn't built with the worker application. +- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL` +- Set the Redis database's connection URL as the value of `REDIS_URL`. + +Feel free to add any other relevant environment variables, such as for integrations and architectural modules. + +### Set Start Command + +The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. + +So, you must run the `start` command from the `.medusa/server` directory. + +If your hosting provider doesn't support setting a current-working directory, set the start command to the following: + +```bash npm2yarn +cd .medusa/server && npm run predeploy && npm run start +``` + +*** + +## 7. Test Deployed Application + +Once the application is deployed and live, go to `/health`, where `` is the URL of the Medusa application in server mode. If the deployment was successful, you’ll see the `OK` response. + +The Medusa Admin is also available at `/app`. + +*** + +## Create Admin User + +If your hosting provider supports running commands in your Medusa application's directory, run the following command to create an admin user: + +```bash +npx medusa user -e admin-medusa@test.com -p supersecret +``` + +Replace the email `admin-medusa@test.com` and password `supersecret` with the credentials you want. + +You can use these credentials to log into the Medusa Admin dashboard. + + # 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. @@ -871,50 +1174,6 @@ The next chapters will guide you to: 3. Expose an API route that allows admin users to create a brand using the workflow. -# Customize Medusa Admin Dashboard - -In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md). - -After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to: - -- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages. -- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md). - -From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard - -*** - -## Next Chapters: View Brands in Dashboard - -In the next chapters, you'll continue with the brands example to: - -- Add a new section to the product details page that shows the product's brand. -- Add a new page in the dashboard that shows all brands in the store. - - -# Extend Core Commerce Features - -In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features. - -In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run. - -Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs: - -- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects. -- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds. -- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow. - -*** - -## Next Chapters: Link Brands to Products Example - -The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to: - -- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md). -- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product. -- Retrieve a product's associated brand's details. - - # Customizations Next Steps: Learn the Fundamentals The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS. @@ -939,62 +1198,48 @@ In the Development Resources documentation, you'll also find step-by-step guides Refer to the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation to learn more. -# Medusa's Architecture +# Extend Core Commerce Features -In this chapter, you'll learn about the architectural layers in Medusa. +In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features. -## HTTP, Workflow, and Module Layers +In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run. -Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes. +Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs: -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). - -![Diagram illustrating the HTTP layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) +- [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. *** -## Database Layer +## Next Chapters: Link Brands to Products Example -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. +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: -Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). +- 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. -![Diagram illustrating the database layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) + +# Customize Medusa Admin Dashboard + +In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md). + +After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to: + +- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages. +- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md). + +From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard *** -## Service Integrations +## Next Chapters: View Brands in Dashboard -Third-party services are integrated through commerce and architectural modules. You also create custom third-party integrations through a custom module. +In the next chapters, you'll continue with the brands example to: -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. - -![Diagram illustrating the commerce modules integration to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175357/Medusa%20Book/service-commerce_qcbdsl.jpg) - -### 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. - -![Diagram illustrating the architectural modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) - -*** - -## Full Diagram of Medusa's Architecture - -The following diagram illustrates Medusa's architecture over the three layers. - -![Full diagram illustrating Medusa's architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) +- Add a new section to the product details page that shows the product's brand. +- Add a new page in the dashboard that shows all brands in the store. # Integrate Third-Party Systems @@ -1206,7 +1451,9 @@ In production, you should always use system environment variables that you set t During development, it's easier to set environment variables in a `.env` file in your repository. -Based on your `NODE_ENV` system environment variable, which is considered as `development` if not set, Medusa will try to load environment variables from the following `.env` files: +Based on your `NODE_ENV` system environment variable, Medusa will try to load environment variables from the following `.env` files: + +As of [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0), `NODE_ENV` defaults to `production` when using `medusa start`. Otherwise, it defaults to `development`. |\`.env\`| |---|---| @@ -1232,6 +1479,121 @@ Since the Medusa Admin is built on top of [Vite](https://vite.dev/), you prefix Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/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. + + +# Events and Subscribers + +In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers. + +## Handle Core Commerce Flows with Events + +When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system. + +Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase. + +You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted. + +![A diagram showcasing an example of how an event is emitted when an order is placed.](https://res.cloudinary.com/dza7lstvk/image/upload/v1732277948/Medusa%20Book/order-placed-event-example_e4e4kw.jpg) + +Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it. + +If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead. + +### List of Emitted Events + +Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md). + +*** + +## How to Create a Subscriber? + +You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to. + +For example, create the file `src/subscribers/order-placed.ts` with the following content: + +![Example of subscriber file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866244/Medusa%20Book/subscriber-dir-overview_pusyeu.jpg) + +```ts title="src/subscribers/product-created.ts" +import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" +import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation" + +export default async function orderPlacedHandler({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const logger = container.resolve("logger") + + logger.info("Sending confirmation email...") + + await sendOrderConfirmationWorkflow(container) + .run({ + input: { + id: data.id, + }, + }) +} + +export const config: SubscriberConfig = { + event: `order.placed`, +} +``` + +This subscriber file exports: + +- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered. +- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber. + +The subscriber function receives an object as a parameter that has the following properties: + +- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case. +- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources. + +In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber. + +*** + +## Test the Subscriber + +To test the subscriber, start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal: + +```bash +info: Processing order.placed which has 1 subscribers +Sending confirmation email... +``` + +The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber. + +*** + +## Event Module + +The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system. + +Medusa provides two Event Modules out of the box: + +- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it. +- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system. + +Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md). + + # Medusa Container In this chapter, you’ll learn about the Medusa container and how to use it. @@ -1382,165 +1744,6 @@ A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md). -# Events and Subscribers - -In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers. - -## Handle Core Commerce Flows with Events - -When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system. - -Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase. - -You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted. - -![A diagram showcasing an example of how an event is emitted when an order is placed.](https://res.cloudinary.com/dza7lstvk/image/upload/v1732277948/Medusa%20Book/order-placed-event-example_e4e4kw.jpg) - -Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it. - -If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead. - -### List of Emitted Events - -Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md). - -*** - -## How to Create a Subscriber? - -You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to. - -For example, create the file `src/subscribers/order-placed.ts` with the following content: - -![Example of subscriber file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866244/Medusa%20Book/subscriber-dir-overview_pusyeu.jpg) - -```ts title="src/subscribers/product-created.ts" -import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" -import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation" - -export default async function orderPlacedHandler({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const logger = container.resolve("logger") - - logger.info("Sending confirmation email...") - - await sendOrderConfirmationWorkflow(container) - .run({ - input: { - id: data.id, - }, - }) -} - -export const config: SubscriberConfig = { - event: `order.placed`, -} -``` - -This subscriber file exports: - -- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered. -- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber. - -The subscriber function receives an object as a parameter that has the following properties: - -- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case. -- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources. - -In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber. - -*** - -## Test the Subscriber - -To test the subscriber, start the Medusa application: - -```bash npm2yarn -npm run dev -``` - -Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal: - -```bash -info: Processing order.placed which has 1 subscribers -Sending confirmation email... -``` - -The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber. - -*** - -## Event Module - -The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system. - -Medusa provides two Event Modules out of the box: - -- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it. -- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system. - -Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md). - - -# Data Models Advanced Guides - -Data models are created and managed in a module. To learn how to create a data model in a custom module, refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). - -In the next chapters, you'll learn about defining data models in more details. You'll learn about: - -- The different property types available. -- How to set a property as a primary key. -- How to create and manage relationships. -- How to configure properties, such as making them nullable or searchable. -- How to manually write migrations. - - -# 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. - -![Diagram showcasing a wishlist plugin installed in a Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1737540762/Medusa%20Book/plugin-diagram_oepiis.jpg) - -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). - - # Module Link In this chapter, you’ll learn what a module link is. @@ -1665,6 +1868,50 @@ 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. + +![Diagram showcasing a wishlist plugin installed in a Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1737540762/Medusa%20Book/plugin-diagram_oepiis.jpg) + +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. @@ -2059,6 +2306,64 @@ 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. +# 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). + +![Diagram illustrating the HTTP layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) + +*** + +## 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). + +![Diagram illustrating the database layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) + +*** + +## 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. + +![Diagram illustrating the commerce modules integration to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175357/Medusa%20Book/service-commerce_qcbdsl.jpg) + +### 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. + +![Diagram illustrating the architectural modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) + +*** + +## Full Diagram of Medusa's Architecture + +The following diagram illustrates Medusa's architecture over the three layers. + +![Full diagram illustrating Medusa's architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) + + # 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. @@ -2125,309 +2430,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. -# General Medusa Application Deployment Guide - -In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform. - -Find how-to guides for specific platforms in [this documentation](https://docs.medusajs.com/resources/deployment/index.html.md). - -Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Medusa Cloud](https://medusajs.com/contact) - -Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. - -With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: - -- Push to deploy. -- Multiple testing environments. -- Preview environments for new PRs. -- Test on production-like data. - -### Prerequisites - -- [Medusa application’s codebase hosted on GitHub repository.](https://docs.medusajs.com/learn/index.html.md) - -## Hosting Provider Requirements - -When you deploy your Medusa application, make sure your chosen hosting provider supports deploying the following resources: - -1. PostgreSQL database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the PostgreSQL database. -2. Redis database: If your hosting provider doesn't support database hosting, you must find another hosting provider for the Redis database. -3. Medusa application in server and worker mode. This means your hosting provider should support deploying two applications or instances from the same codebase. -4. For optimal experience, the hosting provider and plan must offer at least 2GB of RAM. - -*** - -## 1. Configure Medusa Application - -### Worker Mode - -The `workerMode` configuration determines which mode the Medusa application runs in. - -When you deploy the Medusa application, you deploy two instances: one in server mode, and one in worker mode. - -Learn more about the `workerMode` configuration in [this document](https://docs.medusajs.com/resources/references/medusa-config#workermode/index.html.md). - -So, add the following configuration in `medusa-config.ts`: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - // ... - workerMode: process.env.MEDUSA_WORKER_MODE as "shared" | "worker" | "server", - }, -}) -``` - -Later, you’ll set different values of the `MEDUSA_WORKER_MODE` environment variable for each Medusa application deployment or instance. - -### Configure Medusa Admin - -You need to disable the Medusa Admin in the worker Medusa application, while keeping it enabled in the server Medusa application. So, add the following configuration in `medusa-config.ts`: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - // ... - admin: { - disable: process.env.DISABLE_MEDUSA_ADMIN === "true", - }, -}) -``` - -Later, you’ll set different values of the `DISABLE_MEDUSA_ADMIN` environment variable. - -### Configure Redis URL - -The `redisUrl` configuration specifies the connection URL to Redis to store the Medusa server's session. - -Learn more in the [Medusa Configuration documentation](https://docs.medusajs.com/resources/references/medusa-config#redisurl/index.html.md). - -So, add the following configuration in `medusa-config.ts` : - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - // ... - redisUrl: process.env.REDIS_URL, - }, -}) -``` - -*** - -## 2. Add predeploy Script - -Before you start the Medusa application in production, you should always run migrations and sync links. - -So, add the following script in `package.json`: - -```json -"scripts": { - // ... - "predeploy": "medusa db:migrate" -}, -``` - -*** - -## 3. Install Production Modules and Providers - -By default, your Medusa application uses modules and providers useful for development, such as the In-Memory Cache Module or the Local File Module Provider. - -It’s highly recommended to instead use modules and providers suitable for production, including: - -- [Redis Cache Module](https://docs.medusajs.com/resources/architectural-modules/cache/redis/index.html.md) -- [Redis Event Bus Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md) -- [Workflow Engine Redis Module](https://docs.medusajs.com/resources/architectural-modules/workflow-engine/redis/index.html.md) -- [S3 File Module Provider](https://docs.medusajs.com/resources/architectural-modules/file/s3/index.html.md) (or other file module providers production-ready). -- [SendGrid Notification Module Provider](https://docs.medusajs.com/resources/architectural-modules/notification/sendgrid/index.html.md) (or other notification module providers production-ready). - -Then, add these modules in `medusa-config.ts`: - -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" - -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/cache-redis", - options: { - redisUrl: process.env.REDIS_URL, - }, - }, - { - resolve: "@medusajs/medusa/event-bus-redis", - options: { - redisUrl: process.env.REDIS_URL, - }, - }, - { - resolve: "@medusajs/medusa/workflow-engine-redis", - options: { - redis: { - url: process.env.REDIS_URL, - }, - }, - }, - ], -}) -``` - -Check out the [Integrations](https://docs.medusajs.com/resources/integrations/index.html.md) and [Architectural Modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) documentation for other modules and providers to use. - -*** - -## 4. Create PostgreSQL and Redis Databases - -Your Medusa application must connect to PostgreSQL and Redis databases. So, before you deploy it, create production PostgreSQL and Redis databases. - -If your hosting provider doesn't support databases, you can use [Neon for PostgreSQL database hosting](https://neon.tech/), and [Redis Cloud for the Redis database hosting](https://redis.io/cloud/). - -After hosting both databases, keep their connection URLs for the next steps. - -*** - -## 5. Deploy Medusa Application in Server Mode - -As mentioned earlier, you'll deploy two instances or create two deployments of your Medusa application: one in server mode, and the other in worker mode. - -The deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment. - -### Set Environment Variables - -When setting the environment variables of the Medusa application, set the following variables: - -```bash -COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET -JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET -STORE_CORS= # STOREFRONT URL -ADMIN_CORS= # ADMIN URL -AUTH_CORS= # STOREFRONT AND ADMIN URLS, SEPARATED BY COMMAS -DISABLE_MEDUSA_ADMIN=false -MEDUSA_WORKER_MODE=server -PORT=9000 -DATABASE_URL # POSTGRES DATABASE URL -REDIS_URL= # REDIS DATABASE URL -``` - -Where: - -- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret. -- `STORE_CORS`'s value is the URL of your storefront. If you don’t have it yet, you can skip adding it for now. -- `ADMIN_CORS`'s value is the URL of the admin dashboard, which is the same as the server Medusa application. You can add it later if you don't currently have it. -- `AUTH_CORS`'s value is the URLs of any application authenticating users, customers, or other actor types, such as the storefront and admin URLs. The URLs are separated by commas. If you don’t have the URLs yet, you can set its value later. -- Set `DISABLE_MEDUSA_ADMIN`'s value to `false` so that the admin is built with the server application. -- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL` -- Set the Redis database's connection URL as the value of `REDIS_URL`. - -Feel free to add any other relevant environment variables, such as for integrations and architectural modules. - -### Set Start Command - -The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. - -So, you must run the `start` command from the `.medusa/server` directory. - -If your hosting provider doesn't support setting a current-working directory, set the start command to the following: - -```bash npm2yarn -cd .medusa/server && npm run predeploy && npm run start -``` - -### Set Backend URL in Admin Configuration - -After you’ve obtained the Medusa application’s URL, add the following configuration to `medusa-config.ts`: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - // ... - admin: { - // ... - backendUrl: process.env.MEDUSA_BACKEND_URL, - }, -}) -``` - -Then, push the changes to the GitHub repository or deployed application. - -In your hosting provider, add or modify the following environment variables for the Medusa application in server mode: - -```bash -ADMIN_CORS= # MEDUSA APPLICATION URL -AUTH_CORS= # ADD MEDUSA APPLICATION URL -MEDUSA_BACKEND_URL= # URL TO DEPLOYED MEDUSA APPLICATION -``` - -Where you set the value of `ADMIN_CORS` and `MEDUSA_BACKEND_URL` to the Medusa application’s URL, and you add the URL to `AUTH_CORS`. - -Remember to separate URLs in `AUTH_CORS` by commas. - -*** - -## 6. Deploy Medusa Application in Worker Mode - -Next, you'll deploy the Medusa application in worker mode. - -As explained in the previous section, the deployment steps depend on your hosting provider. This section provides the general steps to perform during the deployment. - -### Set Environment Variables - -When setting the environment variables of the Medusa application, set the following variables: - -```bash -COOKIE_SECRET=supersecret # TODO GENERATE SECURE SECRET -JWT_SECRET=supersecret # TODO GENERATE SECURE SECRET -DISABLE_MEDUSA_ADMIN=true -MEDUSA_WORKER_MODE=worker -PORT=9000 -DATABASE_URL # POSTGRES DATABASE URL -REDIS_URL= # REDIS DATABASE URL -``` - -Where: - -- The value of `COOKIE_SECRET` and `JWT_SECRET` must be a randomly generated secret. -- Set `DISABLE_MEDUSA_ADMIN`'s value to `true` so that the admin isn't built with the worker application. -- Set the PostgreSQL database's connection URL as the value of `DATABASE_URL` -- Set the Redis database's connection URL as the value of `REDIS_URL`. - -Feel free to add any other relevant environment variables, such as for integrations and architectural modules. - -### Set Start Command - -The Medusa application's production build, which is created using the `build` command, outputs the Medusa application to `.medusa/server`. - -So, you must run the `start` command from the `.medusa/server` directory. - -If your hosting provider doesn't support setting a current-working directory, set the start command to the following: - -```bash npm2yarn -cd .medusa/server && npm run predeploy && npm run start -``` - -*** - -## 7. Test Deployed Application - -Once the application is deployed and live, go to `/health`, where `` is the URL of the Medusa application in server mode. If the deployment was successful, you’ll see the `OK` response. - -The Medusa Admin is also available at `/app`. - -*** - -## Create Admin User - -If your hosting provider supports running commands in your Medusa application's directory, run the following command to create an admin user: - -```bash -npx medusa user -e admin-medusa@test.com -p supersecret -``` - -Replace the email `admin-medusa@test.com` and password `supersecret` with the credentials you want. - -You can use these credentials to log into the Medusa Admin dashboard. - - # Workflows In this chapter, you’ll learn about workflows and how to define and execute them. @@ -2764,160 +2766,123 @@ To manage that database, such as changing its name or perform operations on it i The next chapters provide examples of writing integration tests for API routes and workflows. -# Guide: Implement Brand Module +# Write Tests for Modules -In this chapter, you'll build a Brand Module that adds a `brand` table to the database and provides data-management features for it. +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. -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. +### Prerequisites -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. +- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) -Learn more about modules in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +## moduleIntegrationTestRunner Utility -## 1. Create Module Directory +`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled. -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. +For example, assuming you have a `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`: -![Directory structure in Medusa project after adding the brand directory](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868844/Medusa%20Book/brand-dir-overview-1_hxwvgx.jpg) +```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/). *** -## 2. Create Data Model +## Run Tests -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. +Run the following command to run your module integration tests: -Learn more about data models in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules#1-create-data-model/index.html.md). +```bash npm2yarn +npm run test:integration:modules +``` -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: +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). -![Directory structure in module after adding the brand data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868920/Medusa%20Book/brand-dir-overview-2_lexhdl.jpg) +This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory. -```ts title="src/modules/brand/models/brand.ts" +*** + +## 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" -export const Brand = model.define("brand", { +const DummyModel = model.define("dummy_model", { 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: - -![Directory structure in module after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868984/Medusa%20Book/brand-dir-overview-3_jo7baj.jpg) - -```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: - -![Directory structure in module after adding the definition file](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869045/Medusa%20Book/brand-dir-overview-4_nf8ymw.jpg) - -```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({ +moduleIntegrationTestRunner({ + moduleModels: [DummyModel], // ... - 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). +jest.setTimeout(60 * 1000) +``` *** -## 6. Generate and Run Migrations +### Other Options and Inputs -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. +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. *** -## Next Step: Create Brand Workflow +## Database Used in Tests -The Brand Module now creates a `brand` table in the database and provides a class to manage its records. +The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end. -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. +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: Create Brand API Route @@ -3128,125 +3093,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: 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. @@ -3385,6 +3231,443 @@ You now have a `createBrandWorkflow` that you can execute to create a brand. In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter. +# Guide: Implement Brand Module + +In this chapter, you'll build a Brand Module that adds a `brand` table to the database and provides data-management features for it. + +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. + +![Directory structure in Medusa project after adding the brand directory](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868844/Medusa%20Book/brand-dir-overview-1_hxwvgx.jpg) + +*** + +## 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: + +![Directory structure in module after adding the brand data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868920/Medusa%20Book/brand-dir-overview-2_lexhdl.jpg) + +```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: + +![Directory structure in module after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868984/Medusa%20Book/brand-dir-overview-3_jo7baj.jpg) + +```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: + +![Directory structure in module after adding the definition file](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869045/Medusa%20Book/brand-dir-overview-4_nf8ymw.jpg) + +```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: 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: + +![The directory structure of the Medusa application after adding the link.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733329897/Medusa%20Book/brands-link-dir-overview_t1rhlp.jpg) + +```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. + +Some API routes, including the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) of the workflow executed in the API route, allowing you to consume those hooks and perform actions with the custom data. + +So, in this chapter, to extend the create product flow and associate a brand with a product, you will: + +- Consume the [productsCreated](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), which is executed within the workflow after the product is created. You'll link the product with the brand passed in the `additional_data` parameter. +- Extend the Create Product API route to allow passing a brand ID in `additional_data`. + +To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md). + +### Prerequisites + +- [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) +- [Defined link between the Brand and Product data models.](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) + +*** + +## 1. Consume the productCreated Hook + +A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. Consuming a workflow hook allows you to extend the features of a workflow and, consequently, the API route that uses it. + +Learn more about the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md). + +The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) has a `productsCreated` hook that runs after the product is created. You'll consume this hook to link the created product with the brand specified in the request parameters. + +To consume the `productsCreated` hook, create the file `src/workflows/hooks/created-product.ts` with the following content: + +![Directory structure after creating the hook's file.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733384338/Medusa%20Book/brands-hook-dir-overview_ltwr5h.jpg) + +```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights} +import { createProductsWorkflow } from "@medusajs/medusa/core-flows" +import { StepResponse } from "@medusajs/framework/workflows-sdk" +import { Modules } from "@medusajs/framework/utils" +import { LinkDefinition } from "@medusajs/framework/types" +import { BRAND_MODULE } from "../../modules/brand" +import BrandModuleService from "../../modules/brand/service" + +createProductsWorkflow.hooks.productsCreated( + (async ({ products, additional_data }, { container }) => { + if (!additional_data?.brand_id) { + return new StepResponse([], []) + } + + const brandModuleService: BrandModuleService = container.resolve( + BRAND_MODULE + ) + // if the brand doesn't exist, an error is thrown. + await brandModuleService.retrieveBrand(additional_data.brand_id as string) + + // TODO link brand to product + }) +) +``` + +Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productCreated`, accepts a step function as a parameter. The step function accepts the following parameters: + +1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products. +2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to resolve framework and commerce tools. + +In the step, if a brand ID is passed in `additional_data`, you resolve the Brand Module's service and use its generated `retrieveBrand` method to retrieve the brand by its ID. The `retrieveBrand` method will throw an error if the brand doesn't exist. + +### Link Brand to Product + +Next, you want to create a link between the created products and the brand. To do so, you use Link, which is a class from the Modules SDK that provides methods to manage linked records. + +Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md). + +To use Link in the `productCreated` hook, replace the `TODO` with the following: + +```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights} +const link = container.resolve("link") +const logger = container.resolve("logger") + +const links: LinkDefinition[] = [] + +for (const product of products) { + links.push({ + [Modules.PRODUCT]: { + product_id: product.id, + }, + [BRAND_MODULE]: { + brand_id: additional_data.brand_id, + }, + }) +} + +await link.create(links) + +logger.info("Linked brand to products") + +return new StepResponse(links, links) +``` + +You resolve Link from the container. Then you loop over the created products to assemble an array of links to be created. After that, you pass the array of links to Link's `create` method, which will link the product and brand records. + +Each property in the link object is the name of a module, and its value is an object having a `{model_name}_id` property, where `{model_name}` is the snake-case name of the module's data model. Its value is the ID of the record to be linked. The link object's properties must be set in the same order as the link configurations passed to `defineLink`. + +![Diagram showcasing how the order of defining a link affects creating the link](https://res.cloudinary.com/dza7lstvk/image/upload/v1733386156/Medusa%20Book/remote-link-brand-product-exp_fhjmg4.jpg) + +Finally, you return an instance of `StepResponse` returning the created links. + +### Dismiss Links in Compensation + +You can pass as a second parameter of the hook a compensation function that undoes what the step did. It receives as a first parameter the returned `StepResponse`'s second parameter, and the step context object as a second parameter. + +To undo creating the links in the hook, pass the following compensation function as a second parameter to `productsCreated`: + +```ts title="src/workflows/hooks/created-product.ts" +createProductsWorkflow.hooks.productsCreated( + // ... + (async (links, { container }) => { + if (!links?.length) { + return + } + + const link = container.resolve("link") + + await link.dismiss(links) + }) +) +``` + +In the compensation function, if the `links` parameter isn't empty, you resolve Link from the container and use its `dismiss` method. This method removes a link between two records. It accepts the same parameter as the `create` method. + +*** + +## 2. Configure Additional Data Validation + +Now that you've consumed the `productCreated` hook, you want to configure the `/admin/products` API route that creates a new product to accept a brand ID in its `additional_data` parameter. + +You configure the properties accepted in `additional_data` in the `src/api/middlewares.ts` that exports middleware configurations. So, create the file (or, if already existing, add to the file) `src/api/middlewares.ts` the following content: + +![Directory structure after adding the middelwares file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733386868/Medusa%20Book/brands-middleware-dir-overview_uczos1.jpg) + +```ts title="src/api/middlewares.ts" +import { defineMiddlewares } from "@medusajs/framework/http" +import { z } from "zod" + +// ... + +export default defineMiddlewares({ + routes: [ + // ... + { + matcher: "/admin/products", + method: ["POST"], + additionalDataValidator: { + brand_id: z.string().optional(), + }, + }, + ], +}) +``` + +Objects in `routes` accept an `additionalDataValidator` property that configures the validation rules for custom properties passed in the `additional_data` request parameter. It accepts an object whose keys are custom property names, and their values are validation rules created using [Zod](https://zod.dev/). + +So, `POST` requests sent to `/admin/products` can now pass the ID of a brand in the `brand_id` property of `additional_data`. + +*** + +## Test it Out + +To test it out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`: + +```bash +curl -X POST 'http://localhost:9000/auth/user/emailpass' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "admin@medusa-test.com", + "password": "supersecret" +}' +``` + +Make sure to replace the email and password in the request body with your user's credentials. + +Then, send a `POST` request to `/admin/products` to create a product, and pass in the `additional_data` parameter a brand's ID: + +```bash +curl -X POST 'http://localhost:9000/admin/products' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer {token}' \ +--data '{ + "title": "Product 1", + "options": [ + { + "title": "Default option", + "values": ["Default option value"] + } + ], + "additional_data": { + "brand_id": "{brand_id}" + } +}' +``` + +Make sure to replace `{token}` with the token you received from the previous request, and `{brand_id}` with the ID of a brand in your application. + +The request creates a product and returns it. + +In the Medusa application's logs, you'll find the message `Linked brand to products`, indicating that the workflow hook handler ran and linked the brand to the products. + +*** + +## Next Steps: Query Linked Brands and Products + +Now that you've extending the create-product flow to link a brand to it, you want to retrieve the brand details of a product. You'll learn how to do so in the next chapter. + + # Create Brands UI Route in Admin In this chapter, you'll add a UI route to the admin dashboard that shows all [brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) in a new page. You'll retrieve the brands from the server and display them in a table with pagination. @@ -3757,242 +4040,11 @@ 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: Add Product's Brand Widget in Admin +# Guide: Query Product's Brands -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. +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. -### Prerequisites - -- [Brands linked to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) - -## 1. Initialize JS SDK - -In your custom widget, you'll retrieve the product's brand by sending a request to the Medusa server. Medusa has a [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) that simplifies sending requests to the server's API routes. - -So, you'll start by configuring the JS SDK. Create the file `src/admin/lib/sdk.ts` with the following content: - -![The directory structure of the Medusa application after adding the file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414606/Medusa%20Book/brands-admin-dir-overview-1_jleg0t.jpg) - -```ts title="src/admin/lib/sdk.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", - }, -}) -``` - -You initialize the SDK passing it the following options: - -- `baseUrl`: The URL to the Medusa server. -- `debug`: Whether to enable logging debug messages. This should only be enabled in development. -- `auth.type`: The authentication method used in the client application, which is `session` in the Medusa Admin dashboard. - -Notice that you use `import.meta.env` to access environment variables in your customizations because the Medusa Admin is built on top of Vite. Learn more in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). - -You can now use the SDK to send requests to the Medusa server. - -Learn more about the JS SDK and its options in [this reference](https://docs.medusajs.com/resources/js-sdk/index.html.md). - -*** - -## 2. Add Widget to Product Details Page - -You'll now add a widget to the product-details page. A widget is a React component that's injected into pre-defined zones in the Medusa Admin dashboard. It's created in a `.tsx` file under the `src/admin/widgets` directory. - -Learn more about widgets in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md). - -To create a widget that shows a product's brand in its details page, create the file `src/admin/widgets/product-brand.tsx` with the following content: - -![Directory structure of the Medusa application after adding the widget](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414684/Medusa%20Book/brands-admin-dir-overview-2_eq5xhi.jpg) - -```tsx title="src/admin/widgets/product-brand.tsx" highlights={highlights} -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { DetailWidgetProps, AdminProduct } from "@medusajs/framework/types" -import { clx, Container, Heading, Text } from "@medusajs/ui" -import { useQuery } from "@tanstack/react-query" -import { sdk } from "../lib/sdk" - -type AdminProductBrand = AdminProduct & { - brand?: { - id: string - name: string - } -} - -const ProductBrandWidget = ({ - data: product, -}: DetailWidgetProps) => { - const { data: queryResult } = useQuery({ - queryFn: () => sdk.admin.product.retrieve(product.id, { - fields: "+brand.*", - }), - queryKey: [["product", product.id]], - }) - const brandName = (queryResult?.product as AdminProductBrand)?.brand?.name - - return ( - -
-
- Brand -
-
-
- - Name - - - - {brandName || "-"} - -
-
- ) -} - -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) - -export default ProductBrandWidget -``` - -A widget's file must export: - -- A React component to be rendered in the specified injection zone. The component must be the file's default export. -- A configuration object created with `defineWidgetConfig` from the Admin Extension SDK. The function receives an object as a parameter that has a `zone` property, whose value is the zone to inject the widget to. - -Since the widget is injected at the top of the product details page, the widget receives the product's details as a parameter. - -In the widget, you use [Tanstack (React) Query](https://tanstack.com/query/latest) to query the Medusa server. Tanstack Query provides features like asynchronous state management and optimized caching. In the `queryFn` function that executes the query, you use the JS SDK to send a request to the [Get Product API Route](https://docs.medusajs.com/api/admin#products_getproductsid), passing `+brand.*` in the `fields` query parameter to retrieve the product's brand. - -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. - -You then render a section that shows the brand's name. In admin customizations, use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md) to maintain a consistent user interface and design in the dashboard. - -*** - -## Test it Out - -To test out your widget, start the Medusa application: - -```bash npm2yarn -npm run dev -``` - -Then, open the admin dashboard at `http://localhost:9000/app`. After you log in, open the page of a product that has a brand. You'll see a new section at the top showing the brand's name. - -![The widget is added as the first section of the product details page.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414415/Medusa%20Book/Screenshot_2024-12-05_at_5.59.25_PM_y85m14.png) - -*** - -## Admin Components Guides - -When building your widget, you may need more complicated components. For example, you may add a form to the above widget to set the product's brand. - -The [Admin Components guides](https://docs.medusajs.com/resources/admin-components/index.html.md) show you how to build and use common components in the Medusa Admin, such as forms, tables, JSON data viewer, and more. The components in the guides also follow the Medusa Admin's design convention. - -*** - -## Next Chapter: Add UI Route for Brands - -In the next chapter, you'll add a UI route that displays the list of brands in your application and allows admin users. - - -# Guide: Define Module Link Between Brand and Product - -In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box. - -Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links. - -A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK. - -In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records. - -Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). - -### Prerequisites - -- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) - -## 1. Define Link - -Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK. - -So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content: - -![The directory structure of the Medusa application after adding the link.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733329897/Medusa%20Book/brands-link-dir-overview_t1rhlp.jpg) - -```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. - -Some API routes, including the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts), accept an `additional_data` request body parameter. This parameter can hold custom data that's passed to the [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) of the workflow executed in the API route, allowing you to consume those hooks and perform actions with the custom data. - -So, in this chapter, to extend the create product flow and associate a brand with a product, you will: - -- Consume the [productsCreated](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow#productsCreated/index.html.md) hook of the [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md), which is executed within the workflow after the product is created. You'll link the product with the brand passed in the `additional_data` parameter. -- Extend the Create Product API route to allow passing a brand ID in `additional_data`. - -To learn more about the `additional_data` property and the API routes that accept additional data, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md). +In this chapter, you'll 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 @@ -4001,195 +4053,127 @@ To learn more about the `additional_data` property and the API routes that accep *** -## 1. Consume the productCreated Hook +## Approach 1: Retrieve Brands in Existing API Routes -A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. Consuming a workflow hook allows you to extend the features of a workflow and, consequently, the API route that uses it. +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 the workflow hooks in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md). +Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). -The [createProductsWorkflow](https://docs.medusajs.com/resources/references/medusa-workflows/createProductsWorkflow/index.html.md) used in the [Create Product API route](https://docs.medusajs.com/api/admin#products_postproducts) has a `productsCreated` hook that runs after the product is created. You'll consume this hook to link the created product with the brand specified in the request parameters. +For example, send the following request to retrieve the list of products with their brands: -To consume the `productsCreated` hook, create the file `src/workflows/hooks/created-product.ts` with the following content: - -![Directory structure after creating the hook's file.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733384338/Medusa%20Book/brands-hook-dir-overview_ltwr5h.jpg) - -```ts title="src/workflows/hooks/created-product.ts" highlights={hook1Highlights} -import { createProductsWorkflow } from "@medusajs/medusa/core-flows" -import { StepResponse } from "@medusajs/framework/workflows-sdk" -import { Modules } from "@medusajs/framework/utils" -import { LinkDefinition } from "@medusajs/framework/types" -import { BRAND_MODULE } from "../../modules/brand" -import BrandModuleService from "../../modules/brand/service" - -createProductsWorkflow.hooks.productsCreated( - (async ({ products, additional_data }, { container }) => { - if (!additional_data?.brand_id) { - return new StepResponse([], []) - } - - const brandModuleService: BrandModuleService = container.resolve( - BRAND_MODULE - ) - // if the brand doesn't exist, an error is thrown. - await brandModuleService.retrieveBrand(additional_data.brand_id as string) - - // TODO link brand to product - }) -) +```bash +curl 'http://localhost:9000/admin/products?fields=+brand.*' \ +--header 'Authorization: Bearer {token}' ``` -Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productCreated`, accepts a step function as a parameter. The step function accepts the following parameters: +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). -1. An object having an `additional_data` property, which is the custom data passed in the request body under `additional_data`. The object will also have properties passed from the workflow to the hook, which in this case is the `products` property that holds an array of the created products. -2. An object of properties related to the step's context. It has a `container` property whose value is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to resolve framework and commerce tools. +Any product that is linked to a brand will have a `brand` property in its object: -In the step, if a brand ID is passed in `additional_data`, you resolve the Brand Module's service and use its generated `retrieveBrand` method to retrieve the brand by its ID. The `retrieveBrand` method will throw an error if the brand doesn't exist. - -### Link Brand to Product - -Next, you want to create a link between the created products and the brand. To do so, you use Link, which is a class from the Modules SDK that provides methods to manage linked records. - -Learn more about Link in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/link/index.html.md). - -To use Link in the `productCreated` hook, replace the `TODO` with the following: - -```ts title="src/workflows/hooks/created-product.ts" highlights={hook2Highlights} -const link = container.resolve("link") -const logger = container.resolve("logger") - -const links: LinkDefinition[] = [] - -for (const product of products) { - links.push({ - [Modules.PRODUCT]: { - product_id: product.id, - }, - [BRAND_MODULE]: { - brand_id: additional_data.brand_id, - }, - }) -} - -await link.create(links) - -logger.info("Linked brand to products") - -return new StepResponse(links, links) -``` - -You resolve Link from the container. Then you loop over the created products to assemble an array of links to be created. After that, you pass the array of links to Link's `create` method, which will link the product and brand records. - -Each property in the link object is the name of a module, and its value is an object having a `{model_name}_id` property, where `{model_name}` is the snake-case name of the module's data model. Its value is the ID of the record to be linked. The link object's properties must be set in the same order as the link configurations passed to `defineLink`. - -![Diagram showcasing how the order of defining a link affects creating the link](https://res.cloudinary.com/dza7lstvk/image/upload/v1733386156/Medusa%20Book/remote-link-brand-product-exp_fhjmg4.jpg) - -Finally, you return an instance of `StepResponse` returning the created links. - -### Dismiss Links in Compensation - -You can pass as a second parameter of the hook a compensation function that undoes what the step did. It receives as a first parameter the returned `StepResponse`'s second parameter, and the step context object as a second parameter. - -To undo creating the links in the hook, pass the following compensation function as a second parameter to `productsCreated`: - -```ts title="src/workflows/hooks/created-product.ts" -createProductsWorkflow.hooks.productsCreated( +```json title="Example Product Object" +{ + "id": "prod_123", // ... - (async (links, { container }) => { - if (!links?.length) { - return - } + "brand": { + "id": "01JEB44M61BRM3ARM2RRMK7GJF", + "name": "Acme", + "created_at": "2024-12-05T09:59:08.737Z", + "updated_at": "2024-12-05T09:59:08.737Z", + "deleted_at": null + } +} +``` - const link = container.resolve("link") +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. - await link.dismiss(links) +*** + +## 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 }) +} ``` -In the compensation function, if the `links` parameter isn't empty, you resolve Link from the container and use its `dismiss` method. This method removes a link between two records. It accepts the same parameter as the `create` method. +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. -## 2. Configure Additional Data Validation +`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response. -Now that you've consumed the `productCreated` hook, you want to configure the `/admin/products` API route that creates a new product to accept a brand ID in its `additional_data` parameter. +### Test it Out -You configure the properties accepted in `additional_data` in the `src/api/middlewares.ts` that exports middleware configurations. So, create the file (or, if already existing, add to the file) `src/api/middlewares.ts` the following content: +To test the API route out, send a `GET` request to `/admin/brands`: -![Directory structure after adding the middelwares file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733386868/Medusa%20Book/brands-middleware-dir-overview_uczos1.jpg) +```bash +curl 'http://localhost:9000/admin/brands' \ +-H 'Authorization: Bearer {token}' +``` -```ts title="src/api/middlewares.ts" -import { defineMiddlewares } from "@medusajs/framework/http" -import { z } from "zod" +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: -export default defineMiddlewares({ - routes: [ - // ... +```json title="Example Response" +{ + "brands": [ { - matcher: "/admin/products", - method: ["POST"], - additionalDataValidator: { - brand_id: z.string().optional(), - }, - }, - ], -}) -``` - -Objects in `routes` accept an `additionalDataValidator` property that configures the validation rules for custom properties passed in the `additional_data` request parameter. It accepts an object whose keys are custom property names, and their values are validation rules created using [Zod](https://zod.dev/). - -So, `POST` requests sent to `/admin/products` can now pass the ID of a brand in the `brand_id` property of `additional_data`. - -*** - -## Test it Out - -To test it out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`: - -```bash -curl -X POST 'http://localhost:9000/auth/user/emailpass' \ --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "admin@medusa-test.com", - "password": "supersecret" -}' -``` - -Make sure to replace the email and password in the request body with your user's credentials. - -Then, send a `POST` request to `/admin/products` to create a product, and pass in the `additional_data` parameter a brand's ID: - -```bash -curl -X POST 'http://localhost:9000/admin/products' \ --H 'Content-Type: application/json' \ --H 'Authorization: Bearer {token}' \ ---data '{ - "title": "Product 1", - "options": [ - { - "title": "Default option", - "values": ["Default option value"] - } - ], - "additional_data": { - "brand_id": "{brand_id}" + "id": "123", + // ... + "products": [ + { + "id": "prod_123", + // ... + } + ] } -}' + ] +} ``` -Make sure to replace `{token}` with the token you received from the previous request, and `{brand_id}` with the ID of a brand in your application. - -The request creates a product and returns it. - -In the Medusa application's logs, you'll find the message `Linked brand to products`, indicating that the workflow hook handler ran and linked the brand to the products. - *** -## Next Steps: Query Linked Brands and Products +## Summary -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. +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 @@ -4464,140 +4448,158 @@ info: API Key: "123" You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day. -# Guide: Query Product's Brands +# Guide: Add Product's Brand Widget in Admin -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 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. ### 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) +- [Brands linked to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) + +## 1. Initialize JS SDK + +In your custom widget, you'll retrieve the product's brand by sending a request to the Medusa server. Medusa has a [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) that simplifies sending requests to the server's API routes. + +So, you'll start by configuring the JS SDK. Create the file `src/admin/lib/sdk.ts` with the following content: + +![The directory structure of the Medusa application after adding the file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414606/Medusa%20Book/brands-admin-dir-overview-1_jleg0t.jpg) + +```ts title="src/admin/lib/sdk.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", + }, +}) +``` + +You initialize the SDK passing it the following options: + +- `baseUrl`: The URL to the Medusa server. +- `debug`: Whether to enable logging debug messages. This should only be enabled in development. +- `auth.type`: The authentication method used in the client application, which is `session` in the Medusa Admin dashboard. + +Notice that you use `import.meta.env` to access environment variables in your customizations because the Medusa Admin is built on top of Vite. Learn more in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). + +You can now use the SDK to send requests to the Medusa server. + +Learn more about the JS SDK and its options in [this reference](https://docs.medusajs.com/resources/js-sdk/index.html.md). *** -## Approach 1: Retrieve Brands in Existing API Routes +## 2. Add Widget to Product Details Page -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. +You'll now add a widget to the product-details page. A widget is a React component that's injected into pre-defined zones in the Medusa Admin dashboard. It's created in a `.tsx` file under the `src/admin/widgets` directory. -Learn more about selecting fields and relations in the [API Reference](https://docs.medusajs.com/api/admin#select-fields-and-relations). +Learn more about widgets in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md). -For example, send the following request to retrieve the list of products with their brands: +To create a widget that shows a product's brand in its details page, create the file `src/admin/widgets/product-brand.tsx` with the following content: -```bash -curl 'http://localhost:9000/admin/products?fields=+brand.*' \ ---header 'Authorization: Bearer {token}' -``` +![Directory structure of the Medusa application after adding the widget](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414684/Medusa%20Book/brands-admin-dir-overview-2_eq5xhi.jpg) -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). +```tsx title="src/admin/widgets/product-brand.tsx" highlights={highlights} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { DetailWidgetProps, AdminProduct } from "@medusajs/framework/types" +import { clx, Container, Heading, Text } from "@medusajs/ui" +import { useQuery } from "@tanstack/react-query" +import { sdk } from "../lib/sdk" -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 +type AdminProductBrand = AdminProduct & { + brand?: { + id: string + name: string } } -``` -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.*"], +const ProductBrandWidget = ({ + data: product, +}: DetailWidgetProps) => { + const { data: queryResult } = useQuery({ + queryFn: () => sdk.admin.product.retrieve(product.id, { + fields: "+brand.*", + }), + queryKey: [["product", product.id]], }) + const brandName = (queryResult?.product as AdminProductBrand)?.brand?.name - res.json({ brands }) + return ( + +
+
+ Brand +
+
+
+ + Name + + + + {brandName || "-"} + +
+
+ ) } + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductBrandWidget ``` -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: +A widget's file must export: -- `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. +- A React component to be rendered in the specified injection zone. The component must be the file's default export. +- A configuration object created with `defineWidgetConfig` from the Admin Extension SDK. The function receives an object as a parameter that has a `zone` property, whose value is the zone to inject the widget to. -`graph` returns an object having a `data` property, which is the retrieved brands. You return the brands in the response. +Since the widget is injected at the top of the product details page, the widget receives the product's details as a parameter. -### Test it Out +In the widget, you use [Tanstack (React) Query](https://tanstack.com/query/latest) to query the Medusa server. Tanstack Query provides features like asynchronous state management and optimized caching. In the `queryFn` function that executes the query, you use the JS SDK to send a request to the [Get Product API Route](https://docs.medusajs.com/api/admin#products_getproductsid), passing `+brand.*` in the `fields` query parameter to retrieve the product's brand. -To test the API route out, send a `GET` request to `/admin/brands`: +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. -```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", - // ... - } - ] - } - ] -} -``` +You then render a section that shows the brand's name. In admin customizations, use components from the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md) to maintain a consistent user interface and design in the dashboard. *** -## Summary +## Test it Out -By following the examples of the previous chapters, you: +To test out your widget, start the Medusa application: -- 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 dev +``` + +Then, open the admin dashboard at `http://localhost:9000/app`. After you log in, open the page of a product that has a brand. You'll see a new section at the top showing the brand's name. + +![The widget is added as the first section of the product details page.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414415/Medusa%20Book/Screenshot_2024-12-05_at_5.59.25_PM_y85m14.png) *** -## Next Steps: Customize Medusa Admin +## Admin Components Guides -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. +When building your widget, you may need more complicated components. For example, you may add a form to the above widget to set the product's brand. -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. +The [Admin Components guides](https://docs.medusajs.com/resources/admin-components/index.html.md) show you how to build and use common components in the Medusa Admin, such as forms, tables, JSON data viewer, and more. The components in the guides also follow the Medusa Admin's design convention. + +*** + +## Next Chapter: Add UI Route for Brands + +In the next chapter, you'll add a UI route that displays the list of brands in your application and allows admin users. # Guide: Schedule Syncing Brands from CMS @@ -5068,6 +5070,51 @@ You can now use the CMS Module's service to perform actions on the third-party C 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. +# 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. @@ -5288,51 +5335,6 @@ The Medusa Admin dashboard can be displayed in languages other than English, whi Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/resources/contribution-guidelines/admin-translations/index.html.md). -# Admin Development Constraints - -This chapter lists some constraints of admin widgets and UI routes. - -## Arrow Functions - -Widget and UI route components must be created as arrow functions. - -```ts highlights={arrowHighlights} -// Don't -function ProductWidget() { - // ... -} - -// Do -const ProductWidget = () => { - // ... -} -``` - -*** - -## Widget Zone - -A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable. - -```ts highlights={zoneHighlights} -// Don't -export const config = defineWidgetConfig({ - zone: `product.details.before`, -}) - -// Don't -const ZONE = "product.details.after" -export const config = defineWidgetConfig({ - zone: ZONE, -}) - -// Do -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) -``` - - # Admin UI Routes In this chapter, you’ll learn how to create a UI route in the admin dashboard. @@ -5563,6 +5565,125 @@ If you run the Medusa application and go to `localhost:9000/app/custom/123`, you To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. +# Admin 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: + +![Example of widget file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867137/Medusa%20Book/widget-dir-overview_dqsbct.jpg) + +```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. + + # Seed Data with Custom CLI Script In this chapter, you'll learn how to seed data using a custom CLI script. @@ -5948,123 +6069,111 @@ createProductsWorkflow.hooks.productsCreated( This updates the products to their original state before adding the brand to their `metadata` property. -# Admin Widgets +# Throwing and Handling Errors -In this chapter, you’ll learn more about widgets and how to use them. +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. -## What is an Admin Widget? +## Throw MedusaError -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. +When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework. -For example, you can add a widget on the product details page that allow admin users to sync products to a third-party service. +The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response. -*** +For example: -## How to Create a Widget? +```ts +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" +import { MedusaError } from "@medusajs/framework/utils" -### Prerequisites +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + if (!req.query.q) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "The `q` query parameter is required." + ) + } -- [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: - -![Example of widget file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867137/Medusa%20Book/widget-dir-overview_dqsbct.jpg) - -```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. +The `MedusaError` class accepts in its constructor two parameters: -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. +1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section. +2. The second is the message to show in the error response. -In the example above, the widget is injected at the top of a product’s details. +### Error Object in Response -The widget component must be created as an arrow function. +The error object returned in the response has two properties: -### Test the Widget +- `type`: The error's type. +- `message`: The error message, if available. +- `code`: A common snake-case code. Its values can be: + - `invalid_request_error` for the `DUPLICATE_ERROR` type. + - `api_error`: for the `DB_ERROR` type. + - `invalid_state_error` for `CONFLICT` error type. + - `unknown_error` for any unidentified error type. + - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor. -To test out the widget, start the Medusa application: +### MedusaError Types -```bash npm2yarn -npm run dev -``` - -Then, open a product’s details page. You’ll find your custom widget at the top of the page. +|Type|Description|Status Code| +|---|---|---|---|---| +|\`DB\_ERROR\`|Indicates a database error.|\`500\`| +|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`| +|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`| +|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`| +|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`| +|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`| +|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`| +|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`| +|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`| +|Other error types|Any other error type results in an |\`500\`| *** -## Props Passed in Detail Pages +## Override Error Handler -Widgets that are injected into a details page receive a `data` prop, which is the main data of the details page. +The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes. -For example, a widget injected into the `product.details.before` zone receives the product's details in the `data` prop: +This error handler will also be used for errors thrown in Medusa's API routes and resources. -```tsx title="src/admin/widgets/product-widget.tsx" highlights={detailHighlights} -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" +For example, create `src/api/middlewares.ts` with the following: + +```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports" import { - DetailWidgetProps, - AdminProduct, -} from "@medusajs/framework/types" + defineMiddlewares, + MedusaNextFunction, + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { MedusaError } from "@medusajs/framework/utils" -// 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 defineMiddlewares({ + errorHandler: ( + error: MedusaError | any, + req: MedusaRequest, + res: MedusaResponse, + next: MedusaNextFunction + ) => { + res.status(400).json({ + error: "Something happened.", + }) + }, }) - -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`. +The `errorHandler` property's value is a function that accepts four parameters: -*** +1. The error thrown. Its type can be `MedusaError` or any other thrown error type. +2. A request object of type `MedusaRequest`. +3. A response object of type `MedusaResponse`. +4. A function of type MedusaNextFunction that executes the next middleware in the stack. -## 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. +This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message. # Handling CORS in API Routes @@ -6421,113 +6530,6 @@ However, if you send a request to `http://localhost:9000/custom/`, the middlewar In general, avoid adding trailing backslashes when sending requests to API routes. -# Throwing and Handling Errors - -In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application. - -## Throw MedusaError - -When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError` from the Medusa Framework. - -The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response. - -For example: - -```ts -import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" -import { MedusaError } from "@medusajs/framework/utils" - -export const GET = async ( - req: MedusaRequest, - res: MedusaResponse -) => { - if (!req.query.q) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "The `q` query parameter is required." - ) - } - - // ... -} -``` - -The `MedusaError` class accepts in its constructor two parameters: - -1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section. -2. The second is the message to show in the error response. - -### Error Object in Response - -The error object returned in the response has two properties: - -- `type`: The error's type. -- `message`: The error message, if available. -- `code`: A common snake-case code. Its values can be: - - `invalid_request_error` for the `DUPLICATE_ERROR` type. - - `api_error`: for the `DB_ERROR` type. - - `invalid_state_error` for `CONFLICT` error type. - - `unknown_error` for any unidentified error type. - - For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor. - -### MedusaError Types - -|Type|Description|Status Code| -|---|---|---|---|---| -|\`DB\_ERROR\`|Indicates a database error.|\`500\`| -|\`DUPLICATE\_ERROR\`|Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.|\`422\`| -|\`INVALID\_ARGUMENT\`|Indicates an error that occurred due to incorrect arguments or other unexpected state.|\`500\`| -|\`INVALID\_DATA\`|Indicates a validation error.|\`400\`| -|\`UNAUTHORIZED\`|Indicates that a user is not authorized to perform an action or access a route.|\`401\`| -|\`NOT\_FOUND\`|Indicates that the requested resource, such as a route or a record, isn't found.|\`404\`| -|\`NOT\_ALLOWED\`|Indicates that an operation isn't allowed.|\`400\`| -|\`CONFLICT\`|Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.|\`409\`| -|\`PAYMENT\_AUTHORIZATION\_ERROR\`|Indicates an error has occurred while authorizing a payment.|\`422\`| -|Other error types|Any other error type results in an |\`500\`| - -*** - -## Override Error Handler - -The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes. - -This error handler will also be used for errors thrown in Medusa's API routes and resources. - -For example, create `src/api/middlewares.ts` with the following: - -```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports" -import { - defineMiddlewares, - MedusaNextFunction, - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { MedusaError } from "@medusajs/framework/utils" - -export default defineMiddlewares({ - errorHandler: ( - error: MedusaError | any, - req: MedusaRequest, - res: MedusaResponse, - next: MedusaNextFunction - ) => { - res.status(400).json({ - error: "Something happened.", - }) - }, -}) -``` - -The `errorHandler` property's value is a function that accepts four parameters: - -1. The error thrown. Its type can be `MedusaError` or any other thrown error type. -2. A request object of type `MedusaRequest`. -3. A response object of type `MedusaResponse`. -4. A function of type MedusaNextFunction that executes the next middleware in the stack. - -This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message. - - # API Route Parameters In this chapter, you’ll learn about path, query, and request body parameters. @@ -6671,6 +6673,108 @@ You can apply validation rules on received body parameters to ensure they match Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/api-routes/validation#how-to-validate-request-body/index.html.md). +# API Route Response + +In this chapter, you'll learn how to send a response in your API route. + +## Send a JSON Response + +To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler. + +For example: + +```ts title="src/api/custom/route.ts" highlights={jsonHighlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + res.json({ + message: "Hello, World!", + }) +} +``` + +This API route returns the following JSON object: + +```json +{ + "message": "Hello, World!" +} +``` + +*** + +## Set Response Status Code + +By default, setting the JSON data using the `json` method returns a response with a `200` status code. + +To change the status code, use the `status` method of the `MedusaResponse` object. + +For example: + +```ts title="src/api/custom/route.ts" highlights={statusHighlight} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + res.status(201).json({ + message: "Hello, World!", + }) +} +``` + +The response of this API route has the status code `201`. + +*** + +## Change Response Content Type + +To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type. + +For example, to create an API route that returns an event stream: + +```ts highlights={streamHighlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }) + + const interval = setInterval(() => { + res.write("Streaming data...\n") + }, 3000) + + req.on("end", () => { + clearInterval(interval) + res.end() + }) +} +``` + +The `writeHead` method accepts two parameters: + +1. The first one is the response's status code. +2. The second is an object of key-value pairs to set the headers of the response. + +This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds. + +*** + +## Do More with Responses + +The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses. + + # Request Body and Query Parameter Validation In this chapter, you'll learn how to validate request body and query parameters in your custom API route. @@ -6920,108 +7024,6 @@ For example, if you omit the `a` parameter, you'll receive a `400` response code To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev). -# API Route Response - -In this chapter, you'll learn how to send a response in your API route. - -## Send a JSON Response - -To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler. - -For example: - -```ts title="src/api/custom/route.ts" highlights={jsonHighlights} -import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" - -export const GET = async ( - req: MedusaRequest, - res: MedusaResponse -) => { - res.json({ - message: "Hello, World!", - }) -} -``` - -This API route returns the following JSON object: - -```json -{ - "message": "Hello, World!" -} -``` - -*** - -## Set Response Status Code - -By default, setting the JSON data using the `json` method returns a response with a `200` status code. - -To change the status code, use the `status` method of the `MedusaResponse` object. - -For example: - -```ts title="src/api/custom/route.ts" highlights={statusHighlight} -import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" - -export const GET = async ( - req: MedusaRequest, - res: MedusaResponse -) => { - res.status(201).json({ - message: "Hello, World!", - }) -} -``` - -The response of this API route has the status code `201`. - -*** - -## Change Response Content Type - -To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type. - -For example, to create an API route that returns an event stream: - -```ts highlights={streamHighlights} -import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" - -export const GET = async ( - req: MedusaRequest, - res: MedusaResponse -) => { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }) - - const interval = setInterval(() => { - res.write("Streaming data...\n") - }, 3000) - - req.on("end", () => { - clearInterval(interval) - res.end() - }) -} -``` - -The `writeHead` method accepts two parameters: - -1. The first one is the response's status code. -2. The second is an object of key-value pairs to set the headers of the response. - -This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds. - -*** - -## Do More with Responses - -The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses. - - # Protected Routes In this chapter, you’ll learn how to create protected routes. @@ -7182,51 +7184,6 @@ export const GET = async ( In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user. -# 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. */} - - # Add Data Model Check Constraints In this chapter, you'll learn how to add check constraints to your data model. @@ -7368,174 +7325,6 @@ export default User In this example, multiple users can’t have the same email. -# 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. - - # Data Model Default Properties In this chapter, you'll learn about the properties available by default in your data model. @@ -7682,6 +7471,46 @@ export default MyCustom This creates a unique composite index on the `name` and `age` properties. +# Infer Type of Data Model + +In this chapter, you'll learn how to infer the type of a data model. + +## How to Infer Type of Data Model? + +Consider you have a `MyCustom` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable. + +Instead, Medusa provides `InferTypeOf` that transforms your data model to a type. + +For example: + +```ts +import { InferTypeOf } from "@medusajs/framework/types" +import { MyCustom } from "../models/my-custom" // relative path to the model + +export type MyCustom = InferTypeOf +``` + +`InferTypeOf` accepts as a type argument the type of the data model. + +Since the `MyCustom` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`. + +You can now use the `MyCustom` type to reference a data model in other types, such as in workflow inputs or service method outputs: + +```ts title="Example Service" +// other imports... +import { InferTypeOf } from "@medusajs/framework/types" +import { MyCustom } from "../models/my-custom" + +type MyCustom = InferTypeOf + +class HelloModuleService extends MedusaService({ MyCustom }) { + async doSomething(): Promise { + // ... + } +} +``` + + # Manage Relationships In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service. @@ -7900,46 +7729,6 @@ const product = await helloModuleService.retrieveProducts( In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product. -# Infer Type of Data Model - -In this chapter, you'll learn how to infer the type of a data model. - -## How to Infer Type of Data Model? - -Consider you have a `MyCustom` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable. - -Instead, Medusa provides `InferTypeOf` that transforms your data model to a type. - -For example: - -```ts -import { InferTypeOf } from "@medusajs/framework/types" -import { MyCustom } from "../models/my-custom" // relative path to the model - -export type MyCustom = InferTypeOf -``` - -`InferTypeOf` accepts as a type argument the type of the data model. - -Since the `MyCustom` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`. - -You can now use the `MyCustom` type to reference a data model in other types, such as in workflow inputs or service method outputs: - -```ts title="Example Service" -// other imports... -import { InferTypeOf } from "@medusajs/framework/types" -import { MyCustom } from "../models/my-custom" - -type MyCustom = InferTypeOf - -class HelloModuleService extends MedusaService({ MyCustom }) { - async doSomething(): Promise { - // ... - } -} -``` - - # Data Model’s Primary Key In this chapter, you’ll learn how to configure the primary key of a data model. @@ -7964,6 +7753,301 @@ 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. + +## What is a Relationship Property? + +A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`. + +When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type. + +You want to create a relation between data models in the same module. + +You want to create a relationship between data models in different modules. Use module links instead. + +*** + +## One-to-One Relationship + +A one-to-one relationship indicates that one record of a data model belongs to or is associated with another. + +To define a one-to-one relationship, create relationship properties in the data models using the following methods: + +1. `hasOne`: indicates that the model has one record of the specified model. +2. `belongsTo`: indicates that the model belongs to one record of the specified model. + +For example: + +```ts highlights={oneToOneHighlights} +import { model } from "@medusajs/framework/utils" + +const User = model.define("user", { + id: model.id().primaryKey(), + email: model.hasOne(() => Email), +}) + +const Email = model.define("email", { + id: model.id().primaryKey(), + user: model.belongsTo(() => User, { + mappedBy: "email", + }), +}) +``` + +In the example above, a user has one email, and an email belongs to one user. + +The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model. + +The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model. + +### Optional Relationship + +To make the relationship optional on the `hasOne` or `belongsTo` side, use the `nullable` method on either property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/configure-properties#nullable-property/index.html.md). + +### One-sided One-to-One Relationship + +If the one-to-one relationship is only defined on one side, pass `undefined` to the `mappedBy` property in the `belongsTo` method. + +For example: + +```ts highlights={oneToOneUndefinedHighlights} +import { model } from "@medusajs/framework/utils" + +const User = model.define("user", { + id: model.id().primaryKey(), +}) + +const Email = model.define("email", { + id: model.id().primaryKey(), + user: model.belongsTo(() => User, { + mappedBy: undefined, + }), +}) +``` + +### One-to-One Relationship in the Database + +When you generate the migrations of data models that have a one-to-one relationship, the migration adds to the table of the data model that has the `belongsTo` property: + +1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `email` table will have a `user_id` column. +2. A foreign key on the `{relation_name}_id` column to the table of the related data model. + +![Diagram illustrating the relation between user and email records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726733492/Medusa%20Book/one-to-one_cj5np3.jpg) + +*** + +## One-to-Many Relationship + +A one-to-many relationship indicates that one record of a data model has many records of another data model. + +To define a one-to-many relationship, create relationship properties in the data models using the following methods: + +1. `hasMany`: indicates that the model has more than one record of the specified model. +2. `belongsTo`: indicates that the model belongs to one record of the specified model. + +For example: + +```ts highlights={oneToManyHighlights} +import { model } from "@medusajs/framework/utils" + +const Store = model.define("store", { + id: model.id().primaryKey(), + products: model.hasMany(() => Product), +}) + +const Product = model.define("product", { + id: model.id().primaryKey(), + store: model.belongsTo(() => Store, { + mappedBy: "products", + }), +}) +``` + +In this example, a store has many products, but a product belongs to one store. + +### Optional Relationship + +To make the relationship optional on the `belongsTo` side, use the `nullable` method on the property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/configure-properties#nullable-property/index.html.md). + +### One-to-Many Relationship in the Database + +When you generate the migrations of data models that have a one-to-many relationship, the migration adds to the table of the data model that has the `belongsTo` property: + +1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `product` table will have a `store_id` column. +2. A foreign key on the `{relation_name}_id` column to the table of the related data model. + +![Diagram illustrating the relation between a store and product records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726733937/Medusa%20Book/one-to-many_d6wtcw.jpg) + +*** + +## Many-to-Many Relationship + +A many-to-many relationship indicates that many records of a data model can be associated with many records of another data model. + +To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method. + +For example: + +```ts highlights={manyToManyHighlights} +import { model } from "@medusajs/framework/utils" + +const Order = model.define("order", { + id: model.id().primaryKey(), + products: model.manyToMany(() => Product, { + mappedBy: "orders", + pivotTable: "order_product", + joinColumn: "order_id", + inverseJoinColumn: "product_id", + }), +}) + +const Product = model.define("product", { + id: model.id().primaryKey(), + orders: model.manyToMany(() => Order, { + mappedBy: "products", + }), +}) +``` + +The `manyToMany` method accepts two parameters: + +1. A function that returns the associated data model. +2. An object of optional configuration. Only one of the data models in the relation can define the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations, and it's considered the owner data model. The object can accept the following properties: + - `mappedBy`: The name of the relationship property in the other data model. If not set, the property's name is inferred from the associated data model's name. + - `pivotTable`: The name of the pivot table created in the database for the many-to-many relation. If not set, the pivot table is inferred by combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`. + - `joinColumn`: The name of the column in the pivot table that points to the owner model's primary key. + - `inverseJoinColumn`: The name of the column in the pivot table that points to the owned model's primary key. + +The `pivotTable`, `joinColumn`, and `inverseJoinColumn` properties are only available after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7). + +Following [Medusa v2.1.0](https://github.com/medusajs/medusa/releases/tag/v2.1.0), if `pivotTable`, `joinColumn`, and `inverseJoinColumn` aren't specified on either model, the owner is decided based on alphabetical order. So, in the example above, the `Order` data model would be the owner. + +In this example, an order is associated with many products, and a product is associated with many orders. Since the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations are defined on the order, it's considered the owner data model. + +### Many-to-Many Relationship in the Database + +When you generate the migrations of data models that have a many-to-many relationship, the migration adds a new pivot table. Its name is either the name you specify in the `pivotTable` configuration or the inferred name combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`. + +The pivot table has a column with the name `{data_model}_id` for each of the data model's tables. It also has foreign keys on each of these columns to their respective tables. + +The pivot table has columns with foreign keys pointing to the primary key of the associated tables. The column's name is either: + +- The value of the `joinColumn` configuration for the owner table, and the `inverseJoinColumn` configuration for the owned table; +- Or the inferred name `{table_name}_id`. + +![Diagram illustrating the relation between order and product records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726734269/Medusa%20Book/many-to-many_fzy5pq.jpg) + +### Many-To-Many with Custom Columns + +To add custom columns to the pivot table between two data models having a many-to-many relationship, you must define a new data model that represents the pivot table. + +For example: + +```ts highlights={manyToManyColumnHighlights} +import { model } from "@medusajs/framework/utils" + +export const Order = model.define("order_test", { + id: model.id().primaryKey(), + products: model.manyToMany(() => Product, { + pivotEntity: () => OrderProduct, + }), +}) + +export const Product = model.define("product_test", { + id: model.id().primaryKey(), + orders: model.manyToMany(() => Order), +}) + +export const OrderProduct = model.define("orders_products", { + id: model.id().primaryKey(), + order: model.belongsTo(() => Order, { + mappedBy: "products", + }), + product: model.belongsTo(() => Product, { + mappedBy: "orders", + }), + metadata: model.json().nullable(), +}) +``` + +The `Order` and `Product` data models have a many-to-many relationship. To add extra columns to the created pivot table, you pass a `pivotEntity` option to the `products` relation in `Order` (since `Order` is the owner). The value of `pivotEntity` is a function that returns the data model representing the pivot table. + +The `OrderProduct` model defines, aside from the ID, the following properties: + +- `order`: A relation that indicates this model belongs to the `Order` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Order` data model. +- `product`: A relation that indicates this model belongs to the `Product` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Product` data model. +- `metadata`: An extra column to add to the pivot table of type `json`. You can add other columns as well to the model. + +*** + +## Set Relationship Name in the Other Model + +The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model. + +This is useful if the relationship property’s name is different from that of the associated data model. + +As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method. + +For example: + +```ts highlights={relationNameHighlights} +import { model } from "@medusajs/framework/utils" + +const User = model.define("user", { + id: model.id().primaryKey(), + email: model.hasOne(() => Email, { + mappedBy: "owner", + }), +}) + +const Email = model.define("email", { + id: model.id().primaryKey(), + owner: model.belongsTo(() => User, { + mappedBy: "email", + }), +}) +``` + +In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`. + +*** + +## Cascades + +When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it. + +For example, if a store is deleted, its products should also be deleted. + +The `cascades` method used on a data model configures which child records an operation is cascaded to. + +For example: + +```ts highlights={highlights} +import { model } from "@medusajs/framework/utils" + +const Store = model.define("store", { + id: model.id().primaryKey(), + products: model.hasMany(() => Product), +}) +.cascades({ + delete: ["products"], +}) + +const Product = model.define("product", { + id: model.id().primaryKey(), + store: model.belongsTo(() => Store, { + mappedBy: "products", + }), +}) +``` + +The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to. + +In the example above, when a store is deleted, its associated products are also deleted. + + # Data Model Property Types In this chapter, you’ll learn about the types of properties in a data model’s schema. @@ -8293,641 +8377,109 @@ const myCustoms = await helloModuleService.listMyCustoms({ This retrieves records that include `John` in their `name` property. -# Data Model Relationships +# Event Data Payload -In this chapter, you’ll learn how to define relationships between data models in your module. +In this chapter, you'll learn how subscribers receive an event's data payload. -## What is a Relationship Property? +## Access Event's Data Payload -A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`. +When events are emitted, they’re emitted with a data payload. -When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type. - -You want to create a relation between data models in the same module. - -You want to create a relationship between data models in different modules. Use module links instead. - -*** - -## One-to-One Relationship - -A one-to-one relationship indicates that one record of a data model belongs to or is associated with another. - -To define a one-to-one relationship, create relationship properties in the data models using the following methods: - -1. `hasOne`: indicates that the model has one record of the specified model. -2. `belongsTo`: indicates that the model belongs to one record of the specified model. +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 highlights={oneToOneHighlights} -import { model } from "@medusajs/framework/utils" - -const User = model.define("user", { - id: model.id().primaryKey(), - email: model.hasOne(() => Email), -}) - -const Email = model.define("email", { - id: model.id().primaryKey(), - user: model.belongsTo(() => User, { - mappedBy: "email", - }), -}) -``` - -In the example above, a user has one email, and an email belongs to one user. - -The `hasOne` and `belongsTo` methods accept a function as the first parameter. The function returns the associated data model. - -The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model. - -### Optional Relationship - -To make the relationship optional on the `hasOne` or `belongsTo` side, use the `nullable` method on either property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/configure-properties#nullable-property/index.html.md). - -### One-sided One-to-One Relationship - -If the one-to-one relationship is only defined on one side, pass `undefined` to the `mappedBy` property in the `belongsTo` method. - -For example: - -```ts highlights={oneToOneUndefinedHighlights} -import { model } from "@medusajs/framework/utils" - -const User = model.define("user", { - id: model.id().primaryKey(), -}) - -const Email = model.define("email", { - id: model.id().primaryKey(), - user: model.belongsTo(() => User, { - mappedBy: undefined, - }), -}) -``` - -### One-to-One Relationship in the Database - -When you generate the migrations of data models that have a one-to-one relationship, the migration adds to the table of the data model that has the `belongsTo` property: - -1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `email` table will have a `user_id` column. -2. A foreign key on the `{relation_name}_id` column to the table of the related data model. - -![Diagram illustrating the relation between user and email records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726733492/Medusa%20Book/one-to-one_cj5np3.jpg) - -*** - -## One-to-Many Relationship - -A one-to-many relationship indicates that one record of a data model has many records of another data model. - -To define a one-to-many relationship, create relationship properties in the data models using the following methods: - -1. `hasMany`: indicates that the model has more than one record of the specified model. -2. `belongsTo`: indicates that the model belongs to one record of the specified model. - -For example: - -```ts highlights={oneToManyHighlights} -import { model } from "@medusajs/framework/utils" - -const Store = model.define("store", { - id: model.id().primaryKey(), - products: model.hasMany(() => Product), -}) - -const Product = model.define("product", { - id: model.id().primaryKey(), - store: model.belongsTo(() => Store, { - mappedBy: "products", - }), -}) -``` - -In this example, a store has many products, but a product belongs to one store. - -### Optional Relationship - -To make the relationship optional on the `belongsTo` side, use the `nullable` method on the property as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/configure-properties#nullable-property/index.html.md). - -### One-to-Many Relationship in the Database - -When you generate the migrations of data models that have a one-to-many relationship, the migration adds to the table of the data model that has the `belongsTo` property: - -1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `product` table will have a `store_id` column. -2. A foreign key on the `{relation_name}_id` column to the table of the related data model. - -![Diagram illustrating the relation between a store and product records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726733937/Medusa%20Book/one-to-many_d6wtcw.jpg) - -*** - -## Many-to-Many Relationship - -A many-to-many relationship indicates that many records of a data model can be associated with many records of another data model. - -To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method. - -For example: - -```ts highlights={manyToManyHighlights} -import { model } from "@medusajs/framework/utils" - -const Order = model.define("order", { - id: model.id().primaryKey(), - products: model.manyToMany(() => Product, { - mappedBy: "orders", - pivotTable: "order_product", - joinColumn: "order_id", - inverseJoinColumn: "product_id", - }), -}) - -const Product = model.define("product", { - id: model.id().primaryKey(), - orders: model.manyToMany(() => Order, { - mappedBy: "products", - }), -}) -``` - -The `manyToMany` method accepts two parameters: - -1. A function that returns the associated data model. -2. An object of optional configuration. Only one of the data models in the relation can define the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations, and it's considered the owner data model. The object can accept the following properties: - - `mappedBy`: The name of the relationship property in the other data model. If not set, the property's name is inferred from the associated data model's name. - - `pivotTable`: The name of the pivot table created in the database for the many-to-many relation. If not set, the pivot table is inferred by combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`. - - `joinColumn`: The name of the column in the pivot table that points to the owner model's primary key. - - `inverseJoinColumn`: The name of the column in the pivot table that points to the owned model's primary key. - -The `pivotTable`, `joinColumn`, and `inverseJoinColumn` properties are only available after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7). - -Following [Medusa v2.1.0](https://github.com/medusajs/medusa/releases/tag/v2.1.0), if `pivotTable`, `joinColumn`, and `inverseJoinColumn` aren't specified on either model, the owner is decided based on alphabetical order. So, in the example above, the `Order` data model would be the owner. - -In this example, an order is associated with many products, and a product is associated with many orders. Since the `pivotTable`, `joinColumn`, and `inverseJoinColumn` configurations are defined on the order, it's considered the owner data model. - -### Many-to-Many Relationship in the Database - -When you generate the migrations of data models that have a many-to-many relationship, the migration adds a new pivot table. Its name is either the name you specify in the `pivotTable` configuration or the inferred name combining the names of the data models' tables in alphabetical order, separating them by `_`, and pluralizing the last name. For example, `order_products`. - -The pivot table has a column with the name `{data_model}_id` for each of the data model's tables. It also has foreign keys on each of these columns to their respective tables. - -The pivot table has columns with foreign keys pointing to the primary key of the associated tables. The column's name is either: - -- The value of the `joinColumn` configuration for the owner table, and the `inverseJoinColumn` configuration for the owned table; -- Or the inferred name `{table_name}_id`. - -![Diagram illustrating the relation between order and product records in the database](https://res.cloudinary.com/dza7lstvk/image/upload/v1726734269/Medusa%20Book/many-to-many_fzy5pq.jpg) - -### Many-To-Many with Custom Columns - -To add custom columns to the pivot table between two data models having a many-to-many relationship, you must define a new data model that represents the pivot table. - -For example: - -```ts highlights={manyToManyColumnHighlights} -import { model } from "@medusajs/framework/utils" - -export const Order = model.define("order_test", { - id: model.id().primaryKey(), - products: model.manyToMany(() => Product, { - pivotEntity: () => OrderProduct, - }), -}) - -export const Product = model.define("product_test", { - id: model.id().primaryKey(), - orders: model.manyToMany(() => Order), -}) - -export const OrderProduct = model.define("orders_products", { - id: model.id().primaryKey(), - order: model.belongsTo(() => Order, { - mappedBy: "products", - }), - product: model.belongsTo(() => Product, { - mappedBy: "orders", - }), - metadata: model.json().nullable(), -}) -``` - -The `Order` and `Product` data models have a many-to-many relationship. To add extra columns to the created pivot table, you pass a `pivotEntity` option to the `products` relation in `Order` (since `Order` is the owner). The value of `pivotEntity` is a function that returns the data model representing the pivot table. - -The `OrderProduct` model defines, aside from the ID, the following properties: - -- `order`: A relation that indicates this model belongs to the `Order` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Order` data model. -- `product`: A relation that indicates this model belongs to the `Product` data model. You set the `mappedBy` option to the many-to-many relation's name in the `Product` data model. -- `metadata`: An extra column to add to the pivot table of type `json`. You can add other columns as well to the model. - -*** - -## Set Relationship Name in the Other Model - -The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model. - -This is useful if the relationship property’s name is different from that of the associated data model. - -As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method. - -For example: - -```ts highlights={relationNameHighlights} -import { model } from "@medusajs/framework/utils" - -const User = model.define("user", { - id: model.id().primaryKey(), - email: model.hasOne(() => Email, { - mappedBy: "owner", - }), -}) - -const Email = model.define("email", { - id: model.id().primaryKey(), - owner: model.belongsTo(() => User, { - mappedBy: "email", - }), -}) -``` - -In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`. - -*** - -## Cascades - -When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it. - -For example, if a store is deleted, its products should also be deleted. - -The `cascades` method used on a data model configures which child records an operation is cascaded to. - -For example: - -```ts highlights={highlights} -import { model } from "@medusajs/framework/utils" - -const Store = model.define("store", { - id: model.id().primaryKey(), - products: model.hasMany(() => Product), -}) -.cascades({ - delete: ["products"], -}) - -const Product = model.define("product", { - id: model.id().primaryKey(), - store: model.belongsTo(() => Store, { - mappedBy: "products", - }), -}) -``` - -The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to. - -In the example above, when a store is deleted, its associated products are also deleted. - - -# Create a Plugin - -In this chapter, you'll learn how to create a Medusa plugin and publish it. - -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. - -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: - -![Directory structure of a plugin project](https://res.cloudinary.com/dza7lstvk/image/upload/v1737019441/Medusa%20Book/project-dir_q4xtri.jpg) - -- `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. - -*** - -## 2. Prepare Plugin - -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: - -```json title="package.json" -{ - "name": "@myorg/plugin-name", - // ... +```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", } ``` -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: +The `event` object has the following properties: -```json title="package.json" -{ - "keywords": [ - "medusa-plugin", - "medusa-v2" - ], - // ... -} -``` +- 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. -## 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. +## List of Events with Data Payload -### Publish and Install Local Package +Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */} -### Prerequisites -- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md) +# Module Link Direction -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. +In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case. -To publish the plugin to the local registry, run the following command in your plugin project: +## Link Direction -```bash title="Plugin project" -npx medusa plugin:publish -``` +The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`. -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", - "./modules/*": "./.medusa/server/src/modules/*/index.js", - "./providers/*": "./.medusa/server/src/providers/*/index.js", - "./*": "./.medusa/server/src/*.js" - } -} -``` - -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. - -The plugin exports the following files and directories: - -- `./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`. -- `./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. +For example, the following defines a link from the `helloModuleService`'s `myCustom` data model to the Product Module's `product` data model: ```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" +export default defineLink( + HelloModule.linkable.myCustom, + ProductModule.linkable.product +) ``` -And you can register a module provider in the Medusa application's `medusa-config.ts` like this: +Whereas the following defines a link from the Product Module's `product` data model to the `helloModuleService`'s `myCustom` data model: -```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... - }, - }, - ], - }, - }, - ], -}) +```ts +export default defineLink( + ProductModule.linkable.product, + HelloModule.linkable.myCustom +) ``` -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) +The above links are two different links that serve different purposes. *** -## 5. Publish Plugin to NPM +## Which Link Direction to Use? -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: +### Extend Data Models -```bash -npx medusa plugin:build +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 +) ``` -The command will compile an output in the `.medusa/server` directory. +### Associate Data Models -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: +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. -```bash -npm publish +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 +) ``` -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. - # Add Columns to a Link @@ -9077,65 +8629,6 @@ await link.create({ ``` -# 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 -) -``` - - # Link In this chapter, you’ll learn what Link is and how to use it to manage links. @@ -9276,6 +8769,174 @@ await link.restore({ ``` +# 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. + + # Query In this chapter, you’ll learn about Query and how to use it to fetch data from modules. @@ -9807,6 +9468,347 @@ In this example, you retrieve products and their associated posts. You also pass To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context). +# Create a Plugin + +In this chapter, you'll learn how to create a Medusa plugin and publish it. + +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. + +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: + +![Directory structure of a plugin project](https://res.cloudinary.com/dza7lstvk/image/upload/v1737019441/Medusa%20Book/project-dir_q4xtri.jpg) + +- `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. + +*** + +## 2. Prepare Plugin + +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: + +```json title="package.json" +{ + "name": "@myorg/plugin-name", + // ... +} +``` + +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" + ], + // ... +} +``` + +*** + +## 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" + } +} +``` + +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. + +The plugin exports the following files and directories: + +- `./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({ + // ... + 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. + + # Architectural Modules In this chapter, you’ll learn about architectural modules. @@ -9838,6 +9840,50 @@ There are different architectural module types including: Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module. +# Commerce Modules + +In this chapter, you'll learn about Medusa's commerce modules. + +## What is a Commerce Module? + +Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more. + +Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart. + +You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md) + +The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically. + +### List of Medusa's Commerce Modules + +Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa. + +*** + +## Use Commerce Modules in Custom Flows + +Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features. + +For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods: + +```ts highlights={highlights} +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" + +export const countProductsStep = createStep( + "count-products", + async ({ }, { container }) => { + const productModuleService = container.resolve("product") + + const [,count] = await productModuleService.listAndCountProducts() + + return new StepResponse(count) + } +) +``` + +Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. + + # Module Container In this chapter, you'll learn about the module's container and how to resolve resources in that container. @@ -9904,48 +9950,348 @@ export default async function helloWorldLoader({ ``` -# Commerce Modules +# Module Isolation -In this chapter, you'll learn about Medusa's commerce modules. +In this chapter, you'll learn how modules are isolated, and what that means for your custom development. -## What is a Commerce Module? +- 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. -Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more. +## How are Modules Isolated? -Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart. +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. -You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md) - -The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically. - -### List of Medusa's Commerce Modules - -Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa. +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. *** -## Use Commerce Modules in Custom Flows +## Why are Modules Isolated -Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features. +Some of the module isolation's benefits include: -For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods: +- 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. -```ts highlights={highlights} -import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +*** -export const countProductsStep = createStep( - "count-products", - async ({ }, { container }) => { - const productModuleService = container.resolve("product") +## How to Extend Data Model of Another Module? - const [,count] = await productModuleService.listAndCountProducts() +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). - return new StepResponse(count) +*** + +## 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) + ) } ) ``` -Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. +The `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. + +## What is a Loader? + +When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup. + +In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules. + +Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module. + +Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). + +*** + +## How to Create a Loader? + +### 1. Implement Loader Function + +You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory. + +For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content: + +![Example of loader file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732865671/Medusa%20Book/loader-dir-overview_eg6vtu.jpg) + +Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). + +```ts title="src/modules/hello/loaders/hello-world.ts" +import { + LoaderOptions, +} from "@medusajs/framework/types" + +export default async function helloWorldLoader({ + container, +}: LoaderOptions) { + const logger = container.resolve("logger") + + logger.info("[helloWorldLoader]: Hello, World!") +} +``` + +The loader file exports an async function, which is the function executed when the application loads. + +The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal. + +Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). + +### 2. Export Loader in Module Definition + +After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it. + +So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`: + +```ts title="src/modules/hello/index.ts" +// other imports... +import helloWorldLoader from "./loaders/hello-world" + +export default Module("hello", { + // ... + loaders: [helloWorldLoader], +}) +``` + +The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts. + +### Test the Loader + +Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, you'll find the following message logged in the terminal: + +```plain +info: [HELLO MODULE] Just started the Medusa application! +``` + +This indicates that the loader in the `hello` module ran and logged this message. + +*** + +## Example: Register Custom MongoDB Connection + +As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module. + +Consider your have a MongoDB module that allows you to perform operations on a MongoDB database. + +### Prerequisites + +- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com) +- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver) + +To connect to the database, you create the following loader in your module: + +```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} +import { LoaderOptions } from "@medusajs/framework/types" +import { asValue } from "awilix" +import { MongoClient } from "mongodb" + +type ModuleOptions = { + connection_url?: string + db_name?: string +} + +export default async function mongoConnectionLoader({ + container, + options, +}: LoaderOptions) { + if (!options.connection_url) { + throw new Error(`[MONGO MDOULE]: connection_url option is required.`) + } + if (!options.db_name) { + throw new Error(`[MONGO MDOULE]: db_name option is required.`) + } + const logger = container.resolve("logger") + + try { + const clientDb = ( + await (new MongoClient(options.connection_url)).connect() + ).db(options.db_name) + + logger.info("Connected to MongoDB") + + container.register( + "mongoClient", + asValue(clientDb) + ) + } catch (e) { + logger.error( + `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}` + ) + } +} +``` + +The loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example: + +```ts title="medusa-config.ts" highlights={optionHighlights} +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "./src/modules/mongo", + options: { + connection_url: process.env.MONGO_CONNECTION_URL, + db_name: process.env.MONGO_DB_NAME, + }, + }, + ], +}) +``` + +Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options: + +- `connection_url`: the URL to connect to the MongoDB database. +- `db_name`: The name of the database to connect to. + +In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options. + +After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters: + +1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client. +2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa. + +### Use Custom Registered Resource in Module's Service + +After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service. + +For example: + +```ts title="src/modules/mongo/service.ts" +import type { Db } from "mongodb" + +type InjectedDependencies = { + mongoClient: Db +} + +export default class MongoModuleService { + private mongoClient_: Db + + constructor({ mongoClient }: InjectedDependencies) { + this.mongoClient_ = mongoClient + } + + async createMovie({ title }: { + title: string + }) { + const moviesCol = this.mongoClient_.collection("movie") + + const insertedMovie = await moviesCol.insertOne({ + title, + }) + + const movie = await moviesCol.findOne({ + _id: insertedMovie.insertedId, + }) + + return movie + } + + async deleteMovie(id: string) { + const moviesCol = this.mongoClient_.collection("movie") + + await moviesCol.deleteOne({ + _id: { + equals: id, + }, + }) + } +} +``` + +The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively. + +Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module: + +```ts title="src/modules/mongo/index.ts" highlights={[["9"]]} +import { Module } from "@medusajs/framework/utils" +import MongoModuleService from "./service" +import mongoConnectionLoader from "./loaders/connection" + +export const MONGO_MODULE = "mongo" + +export default Module(MONGO_MODULE, { + service: MongoModuleService, + loaders: [mongoConnectionLoader], +}) +``` + +### Test it Out + +You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal: + +```bash +info: Connected to MongoDB +``` + +You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database. # Perform Database Operations in a Service @@ -10369,377 +10715,6 @@ 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. - - -# Modules Directory Structure - -In this document, you'll learn about the expected files and directories in your module. - -![Module Directory Structure Example](https://res.cloudinary.com/dza7lstvk/image/upload/v1714379976/Medusa%20Book/modules-dir-overview_nqq7ne.jpg) - -## 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. - - -# Loaders - -In this chapter, you’ll learn about loaders and how to use them. - -## What is a Loader? - -When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup. - -In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules. - -Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module. - -Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). - -*** - -## How to Create a Loader? - -### 1. Implement Loader Function - -You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory. - -For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content: - -![Example of loader file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732865671/Medusa%20Book/loader-dir-overview_eg6vtu.jpg) - -Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). - -```ts title="src/modules/hello/loaders/hello-world.ts" -import { - LoaderOptions, -} from "@medusajs/framework/types" - -export default async function helloWorldLoader({ - container, -}: LoaderOptions) { - const logger = container.resolve("logger") - - logger.info("[helloWorldLoader]: Hello, World!") -} -``` - -The loader file exports an async function, which is the function executed when the application loads. - -The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal. - -Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). - -### 2. Export Loader in Module Definition - -After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it. - -So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`: - -```ts title="src/modules/hello/index.ts" -// other imports... -import helloWorldLoader from "./loaders/hello-world" - -export default Module("hello", { - // ... - loaders: [helloWorldLoader], -}) -``` - -The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts. - -### Test the Loader - -Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application: - -```bash npm2yarn -npm run dev -``` - -Then, you'll find the following message logged in the terminal: - -```plain -info: [HELLO MODULE] Just started the Medusa application! -``` - -This indicates that the loader in the `hello` module ran and logged this message. - -*** - -## Example: Register Custom MongoDB Connection - -As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module. - -Consider your have a MongoDB module that allows you to perform operations on a MongoDB database. - -### Prerequisites - -- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com) -- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver) - -To connect to the database, you create the following loader in your module: - -```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} -import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" -import { MongoClient } from "mongodb" - -type ModuleOptions = { - connection_url?: string - db_name?: string -} - -export default async function mongoConnectionLoader({ - container, - options, -}: LoaderOptions) { - if (!options.connection_url) { - throw new Error(`[MONGO MDOULE]: connection_url option is required.`) - } - if (!options.db_name) { - throw new Error(`[MONGO MDOULE]: db_name option is required.`) - } - const logger = container.resolve("logger") - - try { - const clientDb = ( - await (new MongoClient(options.connection_url)).connect() - ).db(options.db_name) - - logger.info("Connected to MongoDB") - - container.register( - "mongoClient", - asValue(clientDb) - ) - } catch (e) { - logger.error( - `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}` - ) - } -} -``` - -The loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example: - -```ts title="medusa-config.ts" highlights={optionHighlights} -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "./src/modules/mongo", - options: { - connection_url: process.env.MONGO_CONNECTION_URL, - db_name: process.env.MONGO_DB_NAME, - }, - }, - ], -}) -``` - -Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options: - -- `connection_url`: the URL to connect to the MongoDB database. -- `db_name`: The name of the database to connect to. - -In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options. - -After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters: - -1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client. -2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa. - -### Use Custom Registered Resource in Module's Service - -After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service. - -For example: - -```ts title="src/modules/mongo/service.ts" -import type { Db } from "mongodb" - -type InjectedDependencies = { - mongoClient: Db -} - -export default class MongoModuleService { - private mongoClient_: Db - - constructor({ mongoClient }: InjectedDependencies) { - this.mongoClient_ = mongoClient - } - - async createMovie({ title }: { - title: string - }) { - const moviesCol = this.mongoClient_.collection("movie") - - const insertedMovie = await moviesCol.insertOne({ - title, - }) - - const movie = await moviesCol.findOne({ - _id: insertedMovie.insertedId, - }) - - return movie - } - - async deleteMovie(id: string) { - const moviesCol = this.mongoClient_.collection("movie") - - await moviesCol.deleteOne({ - _id: { - equals: id, - }, - }) - } -} -``` - -The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively. - -Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module: - -```ts title="src/modules/mongo/index.ts" highlights={[["9"]]} -import { Module } from "@medusajs/framework/utils" -import MongoModuleService from "./service" -import mongoConnectionLoader from "./loaders/connection" - -export const MONGO_MODULE = "mongo" - -export default Module(MONGO_MODULE, { - service: MongoModuleService, - loaders: [mongoConnectionLoader], -}) -``` - -### Test it Out - -You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal: - -```bash -info: Connected to MongoDB -``` - -You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database. - - # Multiple Services in a Module In this chapter, you'll learn how to use multiple services in a module. @@ -10868,6 +10843,151 @@ 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. + +![Module Directory Structure Example](https://res.cloudinary.com/dza7lstvk/image/upload/v1714379976/Medusa%20Book/modules-dir-overview_nqq7ne.jpg) + +## 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 + ) +} +``` + + # Service Constraints This chapter lists constraints to keep in mind when creating a service. @@ -11081,124 +11201,6 @@ export default HelloModuleService ``` -# 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 - ) -} -``` - - # Scheduled Jobs Number of Executions In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed. @@ -11229,51 +11231,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. -# Access Workflow Errors - -In this chapter, you’ll learn how to access errors that occur during a workflow’s execution. - -## How to Access Workflow Errors? - -By default, when an error occurs in a workflow, it throws that error, and the execution stops. - -You can configure the workflow to return the errors instead so that you can access and handle them differently. - -For example: - -```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import myWorkflow from "../../../workflows/hello-world" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result, errors } = await myWorkflow(req.scope) - .run({ - // ... - throwOnError: false, - }) - - if (errors.length) { - return res.send({ - errors: errors.map((error) => error.error), - }) - } - - res.send(result) -} - -``` - -The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output. - -The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error. - - # Expose a Workflow Hook In this chapter, you'll learn how to expose a hook in your workflow. @@ -11343,6 +11300,51 @@ The hook is available on the workflow's `hooks` property using its name `product You invoke the hook, passing a step function (the hook handler) as a parameter. +# Access Workflow Errors + +In this chapter, you’ll learn how to access errors that occur during a workflow’s execution. + +## How to Access Workflow Errors? + +By default, when an error occurs in a workflow, it throws that error, and the execution stops. + +You can configure the workflow to return the errors instead so that you can access and handle them differently. + +For example: + +```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import myWorkflow from "../../../workflows/hello-world" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result, errors } = await myWorkflow(req.scope) + .run({ + // ... + throwOnError: false, + }) + + if (errors.length) { + return res.send({ + errors: errors.map((error) => error.error), + }) + } + + res.send(result) +} + +``` + +The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output. + +The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error. + + # Compensation Function In this chapter, you'll learn what a compensation function is and how to add it to a step. @@ -12100,136 +12102,6 @@ const step1 = createStep( ``` -# Execute Another Workflow - -In this chapter, you'll learn how to execute a workflow in another. - -## Execute in a Workflow - -To execute a workflow in another, use the `runAsStep` method that every workflow has. - -For example: - -```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports" -import { - createWorkflow, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" - -const workflow = createWorkflow( - "hello-world", - async (input) => { - const products = createProductsWorkflow.runAsStep({ - input: { - products: [ - // ... - ], - }, - }) - - // ... - } -) -``` - -Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter. - -The object has an `input` property to pass input to the workflow. - -*** - -## Preparing Input Data - -If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK. - -Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). - -For example: - -```ts highlights={transformHighlights} collapsibleLines="1-12" -import { - createWorkflow, - transform, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" - -type WorkflowInput = { - title: string -} - -const workflow = createWorkflow( - "hello-product", - async (input: WorkflowInput) => { - const createProductsData = transform({ - input, - }, (data) => [ - { - title: `Hello ${data.input.title}`, - }, - ]) - - const products = createProductsWorkflow.runAsStep({ - input: { - products: createProductsData, - }, - }) - - // ... - } -) -``` - -In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`. - -*** - -## Run Workflow Conditionally - -To run a workflow in another based on a condition, use when-then from the Workflows SDK. - -Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md). - -For example: - -```ts highlights={whenHighlights} collapsibleLines="1-16" -import { - createWorkflow, - when, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" -import { - CreateProductWorkflowInputDTO, -} from "@medusajs/framework/types" - -type WorkflowInput = { - product?: CreateProductWorkflowInputDTO - should_create?: boolean -} - -const workflow = createWorkflow( - "hello-product", - async (input: WorkflowInput) => { - const product = when(input, ({ should_create }) => should_create) - .then(() => { - return createProductsWorkflow.runAsStep({ - input: { - products: [input.product], - }, - }) - }) - } -) -``` - -In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled. - - # Long-Running Workflows In this chapter, you’ll learn what a long-running workflow is and how to configure it. @@ -12592,6 +12464,190 @@ 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`. +# Execute Another Workflow + +In this chapter, you'll learn how to execute a workflow in another. + +## Execute in a Workflow + +To execute a workflow in another, use the `runAsStep` method that every workflow has. + +For example: + +```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports" +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +const workflow = createWorkflow( + "hello-world", + async (input) => { + const products = createProductsWorkflow.runAsStep({ + input: { + products: [ + // ... + ], + }, + }) + + // ... + } +) +``` + +Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter. + +The object has an `input` property to pass input to the workflow. + +*** + +## Preparing Input Data + +If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK. + +Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). + +For example: + +```ts highlights={transformHighlights} collapsibleLines="1-12" +import { + createWorkflow, + transform, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + title: string +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const createProductsData = transform({ + input, + }, (data) => [ + { + title: `Hello ${data.input.title}`, + }, + ]) + + const products = createProductsWorkflow.runAsStep({ + input: { + products: createProductsData, + }, + }) + + // ... + } +) +``` + +In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`. + +*** + +## Run Workflow Conditionally + +To run a workflow in another based on a condition, use when-then from the Workflows SDK. + +Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md). + +For example: + +```ts highlights={whenHighlights} collapsibleLines="1-16" +import { + createWorkflow, + when, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" +import { + CreateProductWorkflowInputDTO, +} from "@medusajs/framework/types" + +type WorkflowInput = { + product?: CreateProductWorkflowInputDTO + should_create?: boolean +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const product = when(input, ({ should_create }) => should_create) + .then(() => { + return createProductsWorkflow.runAsStep({ + input: { + products: [input.product], + }, + }) + }) + } +) +``` + +In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled. + + +# 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`. + + # Store Workflow Executions In this chapter, you'll learn how to store workflow executions in the database and access them later. @@ -12737,6 +12793,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. @@ -12942,146 +13084,6 @@ 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`. - - -# 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). - - # Workflow Hooks In this chapter, you'll learn what a workflow hook is and how to consume them. @@ -14436,145 +14438,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Customer Module - -In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application. - -Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module. - -Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). - -## Customer Features - -- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store. -- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md). - -*** - -## How to Use the Customer Module - -In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. - -You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package. - -For example: - -```ts title="src/workflows/create-customer.ts" highlights={highlights} -import { - createWorkflow, - WorkflowResponse, - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { Modules } from "@medusajs/framework/utils" - -const createCustomerStep = createStep( - "create-customer", - async ({}, { container }) => { - const customerModuleService = container.resolve(Modules.CUSTOMER) - - const customer = await customerModuleService.createCustomers({ - first_name: "Peter", - last_name: "Hayes", - email: "peter.hayes@example.com", - }) - - return new StepResponse({ customer }, customer.id) - }, - async (customerId, { container }) => { - if (!customerId) { - return - } - const customerModuleService = container.resolve(Modules.CUSTOMER) - - await customerModuleService.deleteCustomers([customerId]) - } -) - -export const createCustomerWorkflow = createWorkflow( - "create-customer", - () => { - const { customer } = createCustomerStep() - - return new WorkflowResponse({ - customer, - }) - } -) -``` - -You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers: - -### API Route - -```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { createCustomerWorkflow } from "../../workflows/create-customer" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result } = await createCustomerWorkflow(req.scope) - .run() - - res.send(result) -} -``` - -### Subscriber - -```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import { - type SubscriberConfig, - type SubscriberArgs, -} from "@medusajs/framework" -import { createCustomerWorkflow } from "../workflows/create-customer" - -export default async function handleUserCreated({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const { result } = await createCustomerWorkflow(container) - .run() - - console.log(result) -} - -export const config: SubscriberConfig = { - event: "user.created", -} -``` - -### Scheduled Job - -```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} -import { MedusaContainer } from "@medusajs/framework/types" -import { createCustomerWorkflow } from "../workflows/create-customer" - -export default async function myCustomJob( - container: MedusaContainer -) { - const { result } = await createCustomerWorkflow(container) - .run() - - console.log(result) -} - -export const config = { - name: "run-once-a-day", - schedule: `0 0 * * *`, -} -``` - -Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). - -*** - - # 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. @@ -14717,313 +14580,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# 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). - -*** - - -# Fulfillment Module - -In this section of the documentation, you will find resources to learn more about the Fulfillment Module and how to use it in your application. - -Medusa has fulfillment related features available out-of-the-box through the Fulfillment Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Fulfillment Module. - -Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). - -## Fulfillment Features - -- [Fulfillment Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/item-fulfillment/index.html.md): Create fulfillments and keep track of their status, items, and more. -- [Integrate Third-Party Fulfillment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/fulfillment-provider/index.html.md): Create third-party fulfillment providers to provide customers with shipping options and fulfill their orders. -- [Restrict By Location and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/shipping-option/index.html.md): Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options. -- [Support Different Fulfillment Forms](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts/index.html.md): Support various fulfillment forms, such as shipping or pick up. - -*** - -## How to Use the Fulfillment Module - -In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. - -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-fulfillment.ts" highlights={highlights} -import { - createWorkflow, - WorkflowResponse, - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { Modules } from "@medusajs/framework/utils" - -const createFulfillmentStep = createStep( - "create-fulfillment", - async ({}, { container }) => { - const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT) - - const fulfillment = await fulfillmentModuleService.createFulfillment({ - location_id: "loc_123", - provider_id: "webshipper", - delivery_address: { - country_code: "us", - city: "Strongsville", - address_1: "18290 Royalton Rd", - }, - items: [ - { - title: "Shirt", - sku: "SHIRT", - quantity: 1, - barcode: "123456", - }, - ], - labels: [], - order: {}, - }) - - return new StepResponse({ fulfillment }, fulfillment.id) - }, - async (fulfillmentId, { container }) => { - if (!fulfillmentId) { - return - } - const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT) - - await fulfillmentModuleService.deleteFulfillment(fulfillmentId) - } -) - -export const createFulfillmentWorkflow = createWorkflow( - "create-fulfillment", - () => { - const { fulfillment } = createFulfillmentStep() - - return new WorkflowResponse({ - fulfillment, - }) - } -) -``` - -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 { createFulfillmentWorkflow } from "../../workflows/create-fuilfillment" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result } = await createFulfillmentWorkflow(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 { createFulfillmentWorkflow } from "../workflows/create-fuilfillment" - -export default async function handleUserCreated({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const { result } = await createFulfillmentWorkflow(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 { createFulfillmentWorkflow } from "../workflows/create-fuilfillment" - -export default async function myCustomJob( - container: MedusaContainer -) { - const { result } = await createFulfillmentWorkflow(container) - .run() - - console.log(result) -} - -export const config = { - name: "run-once-a-day", - schedule: `0 0 * * *`, -} -``` - -Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). - -*** - -## Configure Fulfillment Module - -The Fulfillment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) for details on the module's options. - -*** - - # Order Module 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. @@ -15191,6 +14747,7 @@ Learn more about why modules are isolated in [this documentation](https://docs.m - [Authorize, Capture, and Refund Payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md): Authorize, capture, and refund payments for a single resource. - [Payment Collection Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection/index.html.md): Store and manage all payments of a single resources, such as a cart, in payment collections. - [Integrate Third-Party Payment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md): Use payment providers like [Stripe](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md) to handle and process payments, or integrate custom payment providers. +- [Saved Payment Methods](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/account-holder/index.html.md): Save payment methods for customers in third-party payment providers. - [Handle Webhook Events](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/webhook-events/index.html.md): Handle webhook events from third-party providers and process the associated payment. *** @@ -15482,25 +15039,23 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Region Module +# Product Module -In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application. -Medusa has region related features available out-of-the-box through the Region Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Region Module. +Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -*** +## Product Features -## Region Features - -- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings. -- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions. -- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings. +- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options. +- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more. +- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products. *** -## How to Use Region Module's Service +## How to Use the Product Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -15508,7 +15063,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-region.ts" highlights={highlights} +```ts title="src/workflows/create-product.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, @@ -15517,35 +15072,48 @@ import { } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createRegionStep = createStep( - "create-region", +const createProductStep = createStep( + "create-product", async ({}, { container }) => { - const regionModuleService = container.resolve(Modules.REGION) + const productService = container.resolve(Modules.PRODUCT) - const region = await regionModuleService.createRegions({ - name: "Europe", - currency_code: "eur", + const product = await productService.createProducts({ + title: "Medusa Shirt", + options: [ + { + title: "Color", + values: ["Black", "White"], + }, + ], + variants: [ + { + title: "Black Shirt", + options: { + Color: "Black", + }, + }, + ], }) - return new StepResponse({ region }, region.id) + return new StepResponse({ product }, product.id) }, - async (regionId, { container }) => { - if (!regionId) { + async (productId, { container }) => { + if (!productId) { return } - const regionModuleService = container.resolve(Modules.REGION) + const productService = container.resolve(Modules.PRODUCT) - await regionModuleService.deleteRegions([regionId]) + await productService.deleteProducts([productId]) } ) -export const createRegionWorkflow = createWorkflow( - "create-region", +export const createProductWorkflow = createWorkflow( + "create-product", () => { - const { region } = createRegionStep() + const { product } = createProductStep() return new WorkflowResponse({ - region, + product, }) } ) @@ -15560,13 +15128,13 @@ import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { createRegionWorkflow } from "../../workflows/create-region" +import { createProductWorkflow } from "../../workflows/create-product" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await createRegionWorkflow(req.scope) + const { result } = await createProductWorkflow(req.scope) .run() res.send(result) @@ -15580,13 +15148,13 @@ import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { createRegionWorkflow } from "../workflows/create-region" +import { createProductWorkflow } from "../workflows/create-product" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await createRegionWorkflow(container) + const { result } = await createProductWorkflow(container) .run() console.log(result) @@ -15601,12 +15169,12 @@ export const config: SubscriberConfig = { ```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { createRegionWorkflow } from "../workflows/create-region" +import { createProductWorkflow } from "../workflows/create-product" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await createRegionWorkflow(container) + const { result } = await createProductWorkflow(container) .run() console.log(result) @@ -15769,23 +15337,25 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Product Module +# Region Module -In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application. -Medusa has product related features available out-of-the-box through the Product Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module. +Medusa has region related features available out-of-the-box through the Region Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Region Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Product Features +*** -- [Products Management](https://docs.medusajs.com/references/product/models/Product/index.html.md): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options. -- [Product Organization](https://docs.medusajs.com/references/product/models/index.html.md): The Product Module provides different data models used to organize products, including categories, collections, tags, and more. -- [Bundled and Multi-Part Products](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Create and manage inventory kits for a single product, allowing you to implement use cases like bundled or multi-part products. +## Region Features + +- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings. +- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions. +- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings. *** -## How to Use the Product Module +## How to Use Region Module's Service In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -15793,7 +15363,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-product.ts" highlights={highlights} +```ts title="src/workflows/create-region.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, @@ -15802,48 +15372,35 @@ import { } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createProductStep = createStep( - "create-product", +const createRegionStep = createStep( + "create-region", async ({}, { container }) => { - const productService = container.resolve(Modules.PRODUCT) + const regionModuleService = container.resolve(Modules.REGION) - const product = await productService.createProducts({ - title: "Medusa Shirt", - options: [ - { - title: "Color", - values: ["Black", "White"], - }, - ], - variants: [ - { - title: "Black Shirt", - options: { - Color: "Black", - }, - }, - ], + const region = await regionModuleService.createRegions({ + name: "Europe", + currency_code: "eur", }) - return new StepResponse({ product }, product.id) + return new StepResponse({ region }, region.id) }, - async (productId, { container }) => { - if (!productId) { + async (regionId, { container }) => { + if (!regionId) { return } - const productService = container.resolve(Modules.PRODUCT) + const regionModuleService = container.resolve(Modules.REGION) - await productService.deleteProducts([productId]) + await regionModuleService.deleteRegions([regionId]) } ) -export const createProductWorkflow = createWorkflow( - "create-product", +export const createRegionWorkflow = createWorkflow( + "create-region", () => { - const { product } = createProductStep() + const { region } = createRegionStep() return new WorkflowResponse({ - product, + region, }) } ) @@ -15858,13 +15415,13 @@ import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { createProductWorkflow } from "../../workflows/create-product" +import { createRegionWorkflow } from "../../workflows/create-region" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await createProductWorkflow(req.scope) + const { result } = await createRegionWorkflow(req.scope) .run() res.send(result) @@ -15878,13 +15435,13 @@ import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { createProductWorkflow } from "../workflows/create-product" +import { createRegionWorkflow } from "../workflows/create-region" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await createProductWorkflow(container) + const { result } = await createRegionWorkflow(container) .run() console.log(result) @@ -15899,12 +15456,12 @@ export const config: SubscriberConfig = { ```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { createProductWorkflow } from "../workflows/create-product" +import { createRegionWorkflow } from "../workflows/create-region" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await createProductWorkflow(container) + const { result } = await createRegionWorkflow(container) .run() console.log(result) @@ -16640,32 +16197,450 @@ The User Module accepts options for further configurations. Refer to [this docum *** -# API Key Concepts +# Currency Module -In this document, you’ll learn about the different types of API keys, their expiration and verification. +In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application. -## API Key Types +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. -There are two types of API keys: +Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -- `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. +## Currency Features -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). +- [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. *** -## API Key Expiration +## How to Use the Currency Module -An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md). +In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. -The associated token is no longer usable or verifiable. +You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package. + +For example: + +```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). *** -## Token Verification -To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens. +# Customer 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. + +Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module. + +Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). + +## Customer Features + +- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store. +- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md). + +*** + +## How to Use the Customer Module + +In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. + +You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package. + +For example: + +```ts title="src/workflows/create-customer.ts" highlights={highlights} +import { + createWorkflow, + WorkflowResponse, + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import { Modules } from "@medusajs/framework/utils" + +const createCustomerStep = createStep( + "create-customer", + async ({}, { container }) => { + const customerModuleService = container.resolve(Modules.CUSTOMER) + + const customer = await customerModuleService.createCustomers({ + first_name: "Peter", + last_name: "Hayes", + email: "peter.hayes@example.com", + }) + + return new StepResponse({ customer }, customer.id) + }, + async (customerId, { container }) => { + if (!customerId) { + return + } + const customerModuleService = container.resolve(Modules.CUSTOMER) + + await customerModuleService.deleteCustomers([customerId]) + } +) + +export const createCustomerWorkflow = createWorkflow( + "create-customer", + () => { + const { customer } = createCustomerStep() + + return new WorkflowResponse({ + customer, + }) + } +) +``` + +You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers: + +### API Route + +```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { createCustomerWorkflow } from "../../workflows/create-customer" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await createCustomerWorkflow(req.scope) + .run() + + res.send(result) +} +``` + +### Subscriber + +```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { + type SubscriberConfig, + type SubscriberArgs, +} from "@medusajs/framework" +import { createCustomerWorkflow } from "../workflows/create-customer" + +export default async function handleUserCreated({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const { result } = await createCustomerWorkflow(container) + .run() + + console.log(result) +} + +export const config: SubscriberConfig = { + event: "user.created", +} +``` + +### Scheduled Job + +```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} +import { MedusaContainer } from "@medusajs/framework/types" +import { createCustomerWorkflow } from "../workflows/create-customer" + +export default async function myCustomJob( + container: MedusaContainer +) { + const { result } = await createCustomerWorkflow(container) + .run() + + console.log(result) +} + +export const config = { + name: "run-once-a-day", + schedule: `0 0 * * *`, +} +``` + +Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). + +*** + + +# Fulfillment Module + +In this section of the documentation, you will find resources to learn more about the Fulfillment Module and how to use it in your application. + +Medusa has fulfillment related features available out-of-the-box through the Fulfillment Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Fulfillment Module. + +Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). + +## Fulfillment Features + +- [Fulfillment Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/item-fulfillment/index.html.md): Create fulfillments and keep track of their status, items, and more. +- [Integrate Third-Party Fulfillment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/fulfillment-provider/index.html.md): Create third-party fulfillment providers to provide customers with shipping options and fulfill their orders. +- [Restrict By Location and Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/shipping-option/index.html.md): Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options. +- [Support Different Fulfillment Forms](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/concepts/index.html.md): Support various fulfillment forms, such as shipping or pick up. + +*** + +## How to Use the Fulfillment Module + +In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. + +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-fulfillment.ts" highlights={highlights} +import { + createWorkflow, + WorkflowResponse, + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import { Modules } from "@medusajs/framework/utils" + +const createFulfillmentStep = createStep( + "create-fulfillment", + async ({}, { container }) => { + const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT) + + const fulfillment = await fulfillmentModuleService.createFulfillment({ + location_id: "loc_123", + provider_id: "webshipper", + delivery_address: { + country_code: "us", + city: "Strongsville", + address_1: "18290 Royalton Rd", + }, + items: [ + { + title: "Shirt", + sku: "SHIRT", + quantity: 1, + barcode: "123456", + }, + ], + labels: [], + order: {}, + }) + + return new StepResponse({ fulfillment }, fulfillment.id) + }, + async (fulfillmentId, { container }) => { + if (!fulfillmentId) { + return + } + const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT) + + await fulfillmentModuleService.deleteFulfillment(fulfillmentId) + } +) + +export const createFulfillmentWorkflow = createWorkflow( + "create-fulfillment", + () => { + const { fulfillment } = createFulfillmentStep() + + return new WorkflowResponse({ + fulfillment, + }) + } +) +``` + +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 { createFulfillmentWorkflow } from "../../workflows/create-fuilfillment" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await createFulfillmentWorkflow(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 { createFulfillmentWorkflow } from "../workflows/create-fuilfillment" + +export default async function handleUserCreated({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const { result } = await createFulfillmentWorkflow(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 { createFulfillmentWorkflow } from "../workflows/create-fuilfillment" + +export default async function myCustomJob( + container: MedusaContainer +) { + const { result } = await createFulfillmentWorkflow(container) + .run() + + console.log(result) +} + +export const config = { + name: "run-once-a-day", + schedule: `0 0 * * *`, +} +``` + +Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). + +*** + +## Configure Fulfillment Module + +The Fulfillment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) for details on the module's options. + +*** # Links between API Key Module and Other Modules @@ -16764,206 +16739,52 @@ createRemoteLinkStep({ ``` -# Authentication Flows with the Auth Main Service +# Auth Providers -In this document, you'll learn how to use the Auth Module's main service's methods to implement authentication flows and reset a user's password. +In this document, you’ll learn how the Auth Module handles authentication using providers. -## Authentication Methods +## What's an Auth Module Provider? -### Register +An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service. -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, 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. -For example: +### Auth Providers List -```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. +- [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) *** -## Auth Flow 1: Basic Authentication +## Configure Allowed Auth Providers of Actor Types -The basic authentication flow requires first using the `register` method, then the `authenticate` method: +By default, users of all actor types can authenticate with all installed auth module providers. -```ts -const { success, authIdentity, error } = await authModuleService.register( - "emailpass", - // passed to auth provider - { +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"], + }, + // ... + }, // ... - } -) - -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`. - -![Diagram showcasing the basic authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711373749/Medusa%20Resources/basic-auth_lgpqsj.jpg) - -### 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. +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. *** -## Auth Flow 2: Third-Party Service Authentication +## How to Create an Auth Module Provider -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. - -![Diagram showcasing the first part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711374847/Medusa%20Resources/third-party-auth-1_enyedy.jpg) - -### 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. - -![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg) - -*** - -## 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. +Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider. # Auth Identity and Actor Types @@ -17035,393 +16856,32 @@ For example, if you have a custom module with a `Manager` data model, you can au Learn how to create a custom actor type in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md). -# How to Use Authentication Routes +# API Key Concepts -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. +In this document, you’ll learn about the different types of API keys, their expiration and verification. -These routes are added by Medusa's HTTP layer, not the Auth Module. +## API Key Types -## Types of Authentication Flows +There are two types of API keys: -### 1. Basic Authentication Flow +- `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. -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: - -![Diagram showcasing the basic authentication flow between the frontend and the Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1725539370/Medusa%20Resources/basic-auth-routes_pgpjch.jpg) - -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: - -![Diagram showcasing the authentication flow between the frontend, Medusa application, and third-party service](https://res.cloudinary.com/dza7lstvk/image/upload/v1725528159/Medusa%20Resources/Third_Party_Auth_tvf4ng.jpg) - -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. +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). *** -## Register Route +## API Key Expiration -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. +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). -```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. +The associated token is no longer usable or verifiable. *** -## Login Route +## Token Verification -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" -} -``` - - -# 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. +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. # How to Create an Actor Type @@ -17883,118 +17343,6 @@ The Medusa application's configuration accept an `authMethodsPerActor` configura 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) - - # Cart Concepts In this document, you’ll get an overview of the main concepts of a cart. @@ -18469,6 +17817,118 @@ const { data: carts } = useQueryGraphStep({ ``` +# 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) + + # Promotions Adjustments in Carts In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines. @@ -18662,121 +18122,6 @@ await cartModuleService.setLineItemTaxLines( ``` -# 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. - -- [`Cart` data model of Cart Module \<> `Customer` data model](#cart-module). (Read-only). -- [`Order` data model of Order Module \<> `Customer` data model](#order-module). (Read-only). - -*** - -## Cart Module - -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 Concepts In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related. @@ -18875,145 +18220,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 diagram showcasing an example of how data models from the Inventory and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658720/Medusa%20Resources/inventory-product_ejnray.jpg) - -A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity. - -Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md). - -### Retrieve with Query - -To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`: - -### query.graph - -```ts -const { data: inventoryItems } = await query.graph({ - entity: "inventory_item", - fields: [ - "variants.*", - ], -}) - -// inventoryItems.variants -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: inventoryItems } = useQueryGraphStep({ - entity: "inventory_item", - fields: [ - "variants.*", - ], -}) - -// inventoryItems.variants -``` - -### Manage with Link - -To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await link.create({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) -``` - -### createRemoteLinkStep - -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) -``` - -*** - -## Stock Location Module - -Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model. - -### Retrieve with Query - -To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`: - -### query.graph - -```ts -const { data: inventoryLevels } = await query.graph({ - entity: "inventory_level", - fields: [ - "stock_locations.*", - ], -}) - -// inventoryLevels.stock_locations -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: inventoryLevels } = useQueryGraphStep({ - entity: "inventory_level", - fields: [ - "stock_locations.*", - ], -}) - -// inventoryLevels.stock_locations -``` - - # Inventory Kits In this guide, you'll learn how inventory kits can be used in the Medusa application to support use cases like multi-part products, bundled products, and shared inventory across products. @@ -19392,41 +18598,46 @@ 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). -# Links between Currency Module and Other Modules +# Links between Inventory Module and Other Modules -This document showcases the module links defined between the Currency Module and other commerce modules. +This document showcases the module links defined between the Inventory Module and other commerce modules. ## Summary -The Currency 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. -- [`Currency` data model of Store Module \<> `Currency` data model of Currency Module](#store-module). (Read-only). +- [`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). *** -## Store Module +## Product 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. +Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. -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 diagram showcasing an example of how data models from the Inventory and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658720/Medusa%20Resources/inventory-product_ejnray.jpg) + +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 details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`: +To retrieve the 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: stores } = await query.graph({ - entity: "store", +const { data: inventoryItems } = await query.graph({ + entity: "inventory_item", fields: [ - "supported_currencies.currency.*", + "variants.*", ], }) -// stores.supported_currencies +// inventoryItems.variants ``` ### useQueryGraphStep @@ -19436,208 +18647,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: stores } = useQueryGraphStep({ - entity: "store", +const { data: inventoryItems } = useQueryGraphStep({ + entity: "inventory_item", fields: [ - "supported_currencies.currency.*", + "variants.*", ], }) -// 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", - }, - ] -) -``` - -*** - -## 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 diagram showcasing the relation between fulfillment sets, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712329770/Medusa%20Resources/service-zone_awmvfs.jpg) - -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. - -![A diagram showcasing the relation between a fulfillment, fulfillment provider, and shipping option](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331947/Medusa%20Resources/fulfillment-shipping-option_jk9ndp.jpg) - -*** - -## 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. - -![A diagram showcasing the relation between fulfillment and fulfillment items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712332114/Medusa%20Resources/fulfillment-item_etzxb0.jpg) - -*** - -## 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). -- [`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 diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) - -A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. - -![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) - -### 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 +// inventoryItems.variants ``` ### 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): +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 @@ -19647,11 +18669,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.ORDER]: { - order_id: "order_123", + [Modules.PRODUCT]: { + variant_id: "variant_123", }, - [Modules.FULFILLMENT]: { - fulfillment_id: "ful_123", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` @@ -19665,94 +18687,11 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", + [Modules.PRODUCT]: { + variant_id: "variant_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. - -![A diagram showcasing an example of how data models from the Pricing and Fulfillment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716561747/Medusa%20Resources/pricing-fulfillment_spywwa.jpg) - -### 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", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` @@ -19761,33 +18700,23 @@ createRemoteLinkStep({ ## 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. - -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) - -Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. - -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) +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 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`. +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: fulfillmentSets } = await query.graph({ - entity: "fulfillment_set", +const { data: inventoryLevels } = await query.graph({ + entity: "inventory_level", fields: [ - "location.*", + "stock_locations.*", ], }) -// fulfillmentSets.location +// inventoryLevels.stock_locations ``` ### useQueryGraphStep @@ -19797,163 +18726,356 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: fulfillmentSets } = useQueryGraphStep({ - entity: "fulfillment_set", +const { data: inventoryLevels } = useQueryGraphStep({ + entity: "inventory_level", fields: [ - "location.*", + "stock_locations.*", ], }) -// 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", - }, -}) +// inventoryLevels.stock_locations ``` -# Shipping Option +# How to Use Authentication Routes -In this document, you’ll learn about shipping options and their rules. +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. -## What’s a Shipping Option? +These routes are added by Medusa's HTTP layer, not the Auth Module. -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. +## Types of Authentication Flows -When the customer places their order, they choose a shipping option to be used to fulfill their items. +### 1. Basic Authentication Flow -A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md). +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: + +![Diagram showcasing the basic authentication flow between the frontend and the Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1725539370/Medusa%20Resources/basic-auth-routes_pgpjch.jpg) + +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: + +![Diagram showcasing the authentication flow between the frontend, Medusa application, and third-party service](https://res.cloudinary.com/dza7lstvk/image/upload/v1725528159/Medusa%20Resources/Third_Party_Auth_tvf4ng.jpg) + +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. *** -## Service Zone Restrictions +## Register Route -A shipping option is restricted by a service zone, limiting the locations a shipping option be used in. +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. -For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada. - -![A diagram showcasing the relation between shipping options and service zones.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712330831/Medusa%20Resources/shipping-option-service-zone_pobh6k.jpg) - -Service zones can be more restrictive, such as restricting to certain cities or province codes. - -![A diagram showcasing the relation between shipping options, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331186/Medusa%20Resources/shipping-option-service-zone-city_m5sxod.jpg) - -*** - -## 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 diagram showcasing the relation between shipping option and shipping option rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331340/Medusa%20Resources/shipping-option-rule_oosopf.jpg) - -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. - -![A diagram showcasing how a shipping option can have multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331462/Medusa%20Resources/shipping-option-rule-2_ylaqdb.jpg) - -*** - -## Shipping Profile and Types - -A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md). - -A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner. - -*** - -## data Property - -When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process. - -The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment. - - -# Fulfillment Module Options - -In this document, you'll learn about the options of the Fulfillment Module. - -## providers - -The `providers` option is an array of fulfillment module providers. - -When the Medusa application starts, these providers are registered and can be used to process fulfillments. - -For example: - -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" - -// ... - -module.exports = defineConfig({ +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "Whitney_Schultz@gmail.com" // ... - 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: +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. -- `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. +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" +} +``` # Order Concepts @@ -20005,56 +19127,206 @@ An order can have multiple transactions. The sum of these transactions must be e Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md). -# Order Claim +# Authentication Flows with the Auth Main Service -In this document, you’ll learn about order claims. +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. -## What is a Claim? +## Authentication Methods -When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item. +### Register -The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim. +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. *** -## Claim Type +## Auth Flow 1: Basic Authentication -The `Claim` data model has a `type` property whose value indicates the type of the claim: +The basic authentication flow requires first using the `register` method, then the `authenticate` method: -- `refund`: the items are returned, and the customer is refunded. -- `replace`: the items are returned, and the customer receives new items. +```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`. + +![Diagram showcasing the basic authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711373749/Medusa%20Resources/basic-auth_lgpqsj.jpg) + +### 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. *** -## Old and Replacement Items +## Auth Flow 2: Third-Party Service Authentication -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. +The third-party service authentication method requires using the `authenticate` method first: -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). +```ts +const { success, authIdentity, location } = await authModuleService.authenticate( + "google", + // passed to auth provider + { + // ... + } +) -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). +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. + +![Diagram showcasing the first part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711374847/Medusa%20Resources/third-party-auth-1_enyedy.jpg) + +### 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. + +![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg) *** -## Claim Shipping Methods +## Reset Password -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). +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. -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). +For example: -*** +```ts +const { success } = await authModuleService.updateProvider( + "emailpass", + // passed to the auth provider + { + entity_id: "user@example.com", + password: "supersecret", + } +) -## Claim Refund +if (success) { + // password reset successfully +} +``` -If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property. +The method accepts as a first parameter the ID of the provider, and as a second parameter the data necessary to reset the password. -The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim. +In the example above, you use the `emailpass` provider, so you have to pass an object having an `email` and `password` properties. -*** - -## How Claims Impact an Order’s Version - -When a claim is confirmed, the order’s version is incremented. +If the returned `success` property is `true`, the password has reset successfully. # Order Edit @@ -20112,57 +19384,6 @@ 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 Exchange - -In this document, you’ll learn about order exchanges. - -## What is an Exchange? - -An exchange is the replacement of an item that the customer ordered with another. - -A merchant creates the exchange, specifying the items to be replaced and the new items to be sent. - -The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange. - -*** - -## Returned and New Items - -When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer. - -Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). - -The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer. - -*** - -## Exchange Shipping Methods - -An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). - -The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). - -*** - -## Exchange Payment - -The `Exchange` data model has a `difference_due` property that stores the outstanding amount. - -|Condition|Result| -|---|---|---| -|\`difference\_due \< 0\`|Merchant owes the customer a refund of the | -|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the | -|\`difference\_due = 0\`|No payment processing is required.| - -Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). - -*** - -## How Exchanges Impact an Order’s Version - -When an exchange is confirmed, the order’s version is incremented. - - # Links between Order Module and Other Modules This document showcases the module links defined between the Order Module and other commerce modules. @@ -20685,6 +19906,45 @@ const { data: orders } = useQueryGraphStep({ ``` +# Order Change + +In this document, you'll learn about the Order Change data model and possible actions in it. + +## OrderChange Data Model + +The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit. + +Its `change_type` property indicates what the order change is created for: + +1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md). +2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md). +3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md). +4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). + +Once the order change is confirmed, its changes are applied on the order. + +*** + +## Order Change Actions + +The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md). + +The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action. + +The following table lists the possible `action` values that Medusa uses and what `details` they carry. + +|Action|Description|Details| +|---|---|---|---|---| +|\`ITEM\_ADD\`|Add an item to the order.|\`details\`| +|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`| +|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`| +|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`| +|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`| +|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the | +|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the | +|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`| + + # Order Versioning In this document, you’ll learn how an order and its details are versioned. @@ -20837,104 +20097,6 @@ await orderModuleService.setOrderShippingMethodAdjustments( ``` -# Order Change - -In this document, you'll learn about the Order Change data model and possible actions in it. - -## OrderChange Data Model - -The [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md) represents any kind of change to an order, such as a return, exchange, or edit. - -Its `change_type` property indicates what the order change is created for: - -1. `edit`: The order change is making edits to the order, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/edit/index.html.md). -2. `exchange`: The order change is associated with an exchange, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/exchange/index.html.md). -3. `claim`: The order change is associated with a claim, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/claim/index.html.md). -4. `return_request` or `return_receive`: The order change is associated with a return, which you can learn about in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). - -Once the order change is confirmed, its changes are applied on the order. - -*** - -## Order Change Actions - -The actions to perform on the original order by a change, such as adding an item, are represented by the [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md). - -The `OrderChangeAction` has an `action` property that indicates the type of action to perform on the order, and a `details` property that holds more details related to the action. - -The following table lists the possible `action` values that Medusa uses and what `details` they carry. - -|Action|Description|Details| -|---|---|---|---|---| -|\`ITEM\_ADD\`|Add an item to the order.|\`details\`| -|\`ITEM\_UPDATE\`|Update an item in the order.|\`details\`| -|\`RETURN\_ITEM\`|Set an item to be returned.|\`details\`| -|\`RECEIVE\_RETURN\_ITEM\`|Mark a return item as received.|\`details\`| -|\`RECEIVE\_DAMAGED\_RETURN\_ITEM\`|Mark a return item that's damaged as received.|\`details\`| -|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the | -|\`SHIPPING\_ADD\`|Add a shipping method for new or returned items.|No details added. The ID to the shipping method is added in the | -|\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`| - - -# Order Return - -In this document, you’ll learn about order returns. - -## What is a Return? - -A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md). - -A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow. - -![Diagram showcasing the automated RMA flow.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719578128/Medusa%20Resources/return-rma_pzprwq.jpg) - -Once the merchant receives the returned items, they mark the return as received. - -*** - -## Returned Items - -The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem). - -The `ReturnItem` model has two properties storing the item's quantity: - -1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity. -2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity. - -*** - -## Return Shipping Methods - -A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod). - -In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled. - -*** - -## Refund Payment - -The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer. - -The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return. - -*** - -## Returns in Exchanges and Claims - -When a merchant creates an exchange or a claim, it includes returning items from the customer. - -The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for. - -*** - -## How Returns Impact an Order’s Version - -The order’s version is incremented when: - -1. A return is requested. -2. A return is marked as received. - - # Tax Lines in Order Module In this document, you’ll learn about tax lines in an order. @@ -21012,6 +20174,209 @@ 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`. +# 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). + + +# Order Return + +In this document, you’ll learn about order returns. + +## What is a Return? + +A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md). + +A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow. + +![Diagram showcasing the automated RMA flow.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719578128/Medusa%20Resources/return-rma_pzprwq.jpg) + +Once the merchant receives the returned items, they mark the return as received. + +*** + +## Returned Items + +The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem). + +The `ReturnItem` model has two properties storing the item's quantity: + +1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity. +2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity. + +*** + +## Return Shipping Methods + +A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod). + +In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled. + +*** + +## Refund Payment + +The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer. + +The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return. + +*** + +## Returns in Exchanges and Claims + +When a merchant creates an exchange or a claim, it includes returning items from the customer. + +The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for. + +*** + +## How Returns Impact an Order’s Version + +The order’s version is incremented when: + +1. A return is requested. +2. A return is marked as received. + + +# 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 Exchange + +In this document, you’ll learn about order exchanges. + +## What is an Exchange? + +An exchange is the replacement of an item that the customer ordered with another. + +A merchant creates the exchange, specifying the items to be replaced and the new items to be sent. + +The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange. + +*** + +## Returned and New Items + +When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer. + +Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). + +The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer. + +*** + +## Exchange Shipping Methods + +An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). + +The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). + +*** + +## Exchange Payment + +The `Exchange` data model has a `difference_due` property that stores the outstanding amount. + +|Condition|Result| +|---|---|---| +|\`difference\_due \< 0\`|Merchant owes the customer a refund of the | +|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the | +|\`difference\_due = 0\`|No payment processing is required.| + +Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). + +*** + +## How Exchanges Impact an Order’s Version + +When an exchange is confirmed, the order’s version is incremented. + + # Links between Payment Module and Other Modules This document showcases the module links defined between the Payment Module and other commerce modules. @@ -21021,6 +20386,7 @@ This document showcases the module links defined between the Payment Module and 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). @@ -21110,6 +20476,86 @@ createRemoteLinkStep({ *** +## 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. @@ -21365,6 +20811,88 @@ A payment can be refunded multiple times, and each time a refund record is creat ![A diagram showcasing how a payment's multiple refunds are stored](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565555/Medusa%20Resources/payment-refund_lgfvyy.jpg) +# 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. + + +# 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. + +![Diagram showcasing how every payment session has a different payment provider](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565056/Medusa%20Resources/payment-session-provider_guxzqt.jpg) + +*** + +## 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. + + # 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. @@ -21532,53 +21060,40 @@ You can then: 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 +# Webhook Events -In this document, you’ll learn what a payment module provider is. +In this document, you’ll learn how the Payment Module supports listening to webhook events. -## What's a Payment Module Provider? +## What's a Webhook Event? -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. +A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status. -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) +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. *** -## System Payment Provider +## getWebhookActionAndData Method -The Payment Module provides a `system` payment provider that acts as a placeholder payment provider. +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. -It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method. +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`. -## How are Payment Providers Created? +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`. -A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`. +Use that webhook listener in your third-party payment provider's configurations. -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. +![A diagram showcasing the steps of how the getWebhookActionAndData method words](https://res.cloudinary.com/dza7lstvk/image/upload/v1711567415/Medusa%20Resources/payment-webhook_seaocg.jpg) -*** +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. -## Configure Payment Providers +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. -The Payment Module accepts a `providers` option that allows you to register providers in your application. +### Actions After Webhook Payment Processing -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. +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 Collection @@ -21618,75 +21133,6 @@ It also implements the payment flow during checkout as explained in [this docume ![Diagram showcasing the relation between the Payment and Cart modules](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg) -# 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. - -![Diagram showcasing how every payment session has a different payment provider](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565056/Medusa%20Resources/payment-session-provider_guxzqt.jpg) - -*** - -## 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. - - -# 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. - -![A diagram showcasing the steps of how the getWebhookActionAndData method words](https://res.cloudinary.com/dza7lstvk/image/upload/v1711567415/Medusa%20Resources/payment-webhook_seaocg.jpg) - -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. - - # Pricing Concepts In this document, you’ll learn about the main concepts in the Pricing Module. @@ -21903,6 +21349,105 @@ const price = await pricingModuleService.calculatePrices( ### 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 diagram showcasing the relation between the PriceRule and Price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648772/Medusa%20Resources/price-rule-1_vy8bn9.jpg) + +A price can have multiple price rules. + +For example, a price can be restricted by a region and a zip code. + +![A diagram showcasing the relation between the PriceRule and Price with multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709649296/Medusa%20Resources/price-rule-3_pwpocz.jpg) + +*** + +## 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. + +![A diagram showcasing the relation between the PriceSet, PriceList, Price, RuleType, and PriceListRuleValue](https://res.cloudinary.com/dza7lstvk/image/upload/v1709641999/Medusa%20Resources/price-list_zd10yd.jpg) + + +# 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 + + # Links between Pricing Module and Other Modules This document showcases the module links defined between the Pricing Module and other commerce modules. @@ -22085,691 +21630,6 @@ createRemoteLinkStep({ ``` -# 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 diagram showcasing the relation between the PriceRule and Price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648772/Medusa%20Resources/price-rule-1_vy8bn9.jpg) - -A price can have multiple price rules. - -For example, a price can be restricted by a region and a zip code. - -![A diagram showcasing the relation between the PriceRule and Price with multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709649296/Medusa%20Resources/price-rule-3_pwpocz.jpg) - -*** - -## 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. - -![A diagram showcasing the relation between the PriceSet, PriceList, Price, RuleType, and PriceListRuleValue](https://res.cloudinary.com/dza7lstvk/image/upload/v1709641999/Medusa%20Resources/price-list_zd10yd.jpg) - - -# 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 - - -# Links between Region Module and Other Modules - -This document showcases the module links defined between the Region Module and other commerce modules. - -## Summary - -The Region Module has the following links to other modules: - -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. - -- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) -- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) -- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module). - -*** - -## Cart Module - -Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. - -### Retrieve with Query - -To retrieve the carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: - -### query.graph - -```ts -const { data: regions } = await query.graph({ - entity: "region", - fields: [ - "carts.*", - ], -}) - -// regions.carts -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: regions } = useQueryGraphStep({ - entity: "region", - fields: [ - "carts.*", - ], -}) - -// regions.carts -``` - -*** - -## Order Module - -Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. - -### Retrieve with Query - -To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: - -### query.graph - -```ts -const { data: regions } = await query.graph({ - entity: "region", - fields: [ - "orders.*", - ], -}) - -// regions.orders -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: regions } = useQueryGraphStep({ - entity: "region", - fields: [ - "orders.*", - ], -}) - -// regions.orders -``` - -*** - -## Payment Module - -You can specify for each region which payment providers are available for use. - -Medusa defines a module link between the `PaymentProvider` and the `Region` data models. - -![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) - -### Retrieve with Query - -To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`: - -### query.graph - -```ts -const { data: regions } = await query.graph({ - entity: "region", - fields: [ - "payment_providers.*", - ], -}) - -// regions.payment_providers -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: regions } = useQueryGraphStep({ - entity: "region", - fields: [ - "payment_providers.*", - ], -}) - -// regions.payment_providers -``` - -### Manage with Link - -To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await link.create({ - [Modules.REGION]: { - region_id: "reg_123", - }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", - }, -}) -``` - -### createRemoteLinkStep - -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.REGION]: { - region_id: "reg_123", - }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", - }, -}) -``` - - -# 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. - - -# 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. - -![A diagram showcasing the relation between the Campaign and Promotion data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899225/Medusa%20Resources/campagin-promotion_hh3qsi.jpg) - -*** - -## 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. - -![A diagram showcasing the relation between the Campaign and CampaignBudget data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899463/Medusa%20Resources/campagin-budget_rvqlmi.jpg) - - -# 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. - -![A diagram showcasing the target\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898273/Medusa%20Resources/application-method-target-rules_hqaymz.jpg) - -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. - -![A diagram showcasing the buy\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898453/Medusa%20Resources/application-method-buy-rules_djjuhw.jpg) - -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 diagram showcasing the relation between Promotion and PromotionRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1709833196/Medusa%20Resources/promotion-promotion-rule_msbx0w.jpg) - -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`. - -![A diagram showcasing the relation between PromotionRule and PromotionRuleValue when a rule has multiple values](https://res.cloudinary.com/dza7lstvk/image/upload/v1709897383/Medusa%20Resources/promotion-promotion-rule-multiple_hctpmt.jpg) - -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. - -![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) - -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. - -![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) - -### Retrieve with Query - -To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: - -### query.graph - -```ts -const { data: promotions } = await query.graph({ - entity: "promotion", - fields: [ - "orders.*", - ], -}) - -// promotions.orders -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: promotions } = useQueryGraphStep({ - entity: "promotion", - fields: [ - "orders.*", - ], -}) - -// promotions.orders -``` - -### Manage with Link - -To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await link.create({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` - -### createRemoteLinkStep - -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` - - # Links between Product Module and Other Modules This document showcases the module links defined between the Product Module and other commerce modules. @@ -22781,6 +21641,7 @@ 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). @@ -22833,6 +21694,87 @@ const { data: variants } = useQueryGraphStep({ *** +## 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. @@ -23195,6 +22137,592 @@ The following guides provide more details on inventory management in the Medusa - [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. + +![A diagram showcasing the target\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898273/Medusa%20Resources/application-method-target-rules_hqaymz.jpg) + +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. + +![A diagram showcasing the buy\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898453/Medusa%20Resources/application-method-buy-rules_djjuhw.jpg) + +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 diagram showcasing the relation between Promotion and PromotionRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1709833196/Medusa%20Resources/promotion-promotion-rule_msbx0w.jpg) + +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`. + +![A diagram showcasing the relation between PromotionRule and PromotionRuleValue when a rule has multiple values](https://res.cloudinary.com/dza7lstvk/image/upload/v1709897383/Medusa%20Resources/promotion-promotion-rule-multiple_hctpmt.jpg) + +In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion. + + +# Campaign + +In this document, you'll learn about campaigns. + +## 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. + +![A diagram showcasing the relation between the Campaign and Promotion data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899225/Medusa%20Resources/campagin-promotion_hh3qsi.jpg) + +*** + +## 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. + +![A diagram showcasing the relation between the Campaign and CampaignBudget data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899463/Medusa%20Resources/campagin-budget_rvqlmi.jpg) + + +# 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. + +![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) + +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. + +![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) + +### Retrieve with Query + +To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: + +### query.graph + +```ts +const { data: promotions } = await query.graph({ + entity: "promotion", + fields: [ + "orders.*", + ], +}) + +// promotions.orders +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: promotions } = useQueryGraphStep({ + entity: "promotion", + fields: [ + "orders.*", + ], +}) + +// promotions.orders +``` + +### Manage with Link + +To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): + +### link.create + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.create({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.PROMOTION]: { + promotion_id: "promo_123", + }, +}) +``` + +### createRemoteLinkStep + +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" + +// ... + +createRemoteLinkStep({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.PROMOTION]: { + promotion_id: "promo_123", + }, +}) +``` + + +# Links between Region Module and Other Modules + +This document showcases the module links defined between the Region Module and other commerce modules. + +## Summary + +The Region Module has the following links to other modules: + +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. + +- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) +- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) +- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module). + +*** + +## Cart Module + +Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. + +### Retrieve with Query + +To retrieve the carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: + +### query.graph + +```ts +const { data: regions } = await query.graph({ + entity: "region", + fields: [ + "carts.*", + ], +}) + +// regions.carts +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: regions } = useQueryGraphStep({ + entity: "region", + fields: [ + "carts.*", + ], +}) + +// regions.carts +``` + +*** + +## Order Module + +Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. + +### Retrieve with Query + +To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: + +### query.graph + +```ts +const { data: regions } = await query.graph({ + entity: "region", + fields: [ + "orders.*", + ], +}) + +// regions.orders +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: regions } = useQueryGraphStep({ + entity: "region", + fields: [ + "orders.*", + ], +}) + +// regions.orders +``` + +*** + +## Payment Module + +You can specify for each region which payment providers are available for use. + +Medusa defines a module link between the `PaymentProvider` and the `Region` data models. + +![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) + +### Retrieve with Query + +To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`: + +### query.graph + +```ts +const { data: regions } = await query.graph({ + entity: "region", + fields: [ + "payment_providers.*", + ], +}) + +// regions.payment_providers +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: regions } = useQueryGraphStep({ + entity: "region", + fields: [ + "payment_providers.*", + ], +}) + +// regions.payment_providers +``` + +### Manage with Link + +To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): + +### link.create + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.create({ + [Modules.REGION]: { + region_id: "reg_123", + }, + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", + }, +}) +``` + +### createRemoteLinkStep + +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" + +// ... + +createRemoteLinkStep({ + [Modules.REGION]: { + region_id: "reg_123", + }, + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", + }, +}) +``` + + # Links between Sales Channel Module and Other Modules This document showcases the module links defined between the Sales Channel Module and other commerce modules. @@ -23865,49 +23393,6 @@ const { data: stores } = useQueryGraphStep({ ``` -# Tax Module Options - -In this document, you'll learn about the options of the Tax Module. - -## providers - -The `providers` option is an array of either tax module providers or path to a file that defines a tax provider. - -When the Medusa application starts, these providers are registered and can be used to retrieve tax lines. - -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" - -// ... - -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/tax", - options: { - providers: [ - { - resolve: "./path/to/my-provider", - id: "my-provider", - options: { - // ... - }, - }, - ], - }, - }, - ], -}) -``` - -The objects in the array accept the following properties: - -- `resolve`: A string indicating the package name of the module provider or the path to it. -- `id`: A string indicating the provider's unique name or ID. -- `options`: An optional object of the module provider's options. - - # Tax Calculation with the Tax Provider In this document, you’ll learn how tax lines are calculated and what a tax provider is. @@ -23985,6 +23470,62 @@ 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 Module Options + +In this document, you'll learn about the options of the Tax Module. + +## providers + +The `providers` option is an array of either tax module providers or path to a file that defines a tax provider. + +When the Medusa application starts, these providers are registered and can be used to retrieve tax lines. + +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/tax", + options: { + providers: [ + { + resolve: "./path/to/my-provider", + id: "my-provider", + options: { + // ... + }, + }, + ], + }, + }, + ], +}) +``` + +The objects in the array accept the following properties: + +- `resolve`: A string indicating the package name of the module provider or the path to it. +- `id`: A string indicating the provider's unique name or ID. +- `options`: An optional object of the module provider's options. + + +# 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. @@ -24021,17 +23562,41 @@ 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 +# User Module Options -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 options of the User Module. -## What is a Tax Region? +## Module Options -A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves. +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" -Tax regions can inherit settings and rules from a parent tax region. +// ... -Each tax region has tax rules and a tax provider. +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/user", + options: { + jwt_secret: process.env.JWT_SECRET, + }, + }, + ], +}) +``` + +|Option|Description|Required| +|---|---|---|---|---| +|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes| + +### Environment Variables + +Make sure to add the necessary environment variables for the above options in `.env`: + +```bash +JWT_SECRET=supersecret +``` # User Creation Flows @@ -24112,11 +23677,750 @@ if (!count) { ``` -# User Module Options +# Links between Currency Module and Other Modules -In this document, you'll learn about the options of the User Module. +This document showcases the module links defined between the Currency Module and other commerce modules. -## Module Options +## 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 +``` + + +# 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 +``` + + +# 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 diagram showcasing the relation between fulfillment sets, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712329770/Medusa%20Resources/service-zone_awmvfs.jpg) + +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. + +![A diagram showcasing the relation between a fulfillment, fulfillment provider, and shipping option](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331947/Medusa%20Resources/fulfillment-shipping-option_jk9ndp.jpg) + +*** + +## 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. + +![A diagram showcasing the relation between fulfillment and fulfillment items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712332114/Medusa%20Resources/fulfillment-item_etzxb0.jpg) + +*** + +## 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 diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) + +A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. + +![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) + +### 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. + +![A diagram showcasing an example of how data models from the Pricing and Fulfillment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716561747/Medusa%20Resources/pricing-fulfillment_spywwa.jpg) + +### 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. + +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) + +Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. + +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) + +### Retrieve with Query + +To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`: + +To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`. + +### query.graph + +```ts +const { data: fulfillmentSets } = await query.graph({ + entity: "fulfillment_set", + fields: [ + "location.*", + ], +}) + +// fulfillmentSets.location +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: fulfillmentSets } = useQueryGraphStep({ + entity: "fulfillment_set", + fields: [ + "location.*", + ], +}) + +// fulfillmentSets.location +``` + +### Manage with Link + +To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): + +### link.create + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.create({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, +}) +``` + +### createRemoteLinkStep + +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" + +// ... + +createRemoteLinkStep({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, +}) +``` + + +# Fulfillment Module Options + +In this document, you'll learn about the options of the Fulfillment Module. + +## providers + +The `providers` option is an array of fulfillment module providers. + +When the Medusa application starts, these providers are registered and can be used to process fulfillments. + +For example: ```ts title="medusa-config.ts" import { Modules } from "@medusajs/framework/utils" @@ -24127,26 +24431,92 @@ module.exports = defineConfig({ // ... modules: [ { - resolve: "@medusajs/user", + resolve: "@medusajs/medusa/fulfillment", options: { - jwt_secret: process.env.JWT_SECRET, + providers: [ + { + resolve: `@medusajs/medusa/fulfillment-manual`, + id: "manual", + options: { + // provider options... + }, + }, + ], }, }, ], }) ``` -|Option|Description|Required| -|---|---|---|---|---| -|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes| +The `providers` option is an array of objects that accept the following properties: -### Environment Variables +- `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. -Make sure to add the necessary environment variables for the above options in `.env`: -```bash -JWT_SECRET=supersecret -``` +# 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. + +![A diagram showcasing the relation between shipping options and service zones.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712330831/Medusa%20Resources/shipping-option-service-zone_pobh6k.jpg) + +Service zones can be more restrictive, such as restricting to certain cities or province codes. + +![A diagram showcasing the relation between shipping options, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331186/Medusa%20Resources/shipping-option-service-zone-city_m5sxod.jpg) + +*** + +## 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 diagram showcasing the relation between shipping option and shipping option rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331340/Medusa%20Resources/shipping-option-rule_oosopf.jpg) + +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. + +![A diagram showcasing how a shipping option can have multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331462/Medusa%20Resources/shipping-option-rule-2_ylaqdb.jpg) + +*** + +## 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. # Emailpass Auth Module Provider @@ -24211,6 +24581,88 @@ const hashConfig = \{ - [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) +# 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). + + # 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. @@ -24298,88 +24750,6 @@ The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednass - [How to implement Google social login in the storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). -# GitHub Auth Module Provider - -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). - - # Stripe Module Provider In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module. @@ -24747,597 +25117,600 @@ For each product variant, you: ## Workflows -- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md) -- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md) -- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md) -- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md) -- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/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) -- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md) -- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) -- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md) -- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md) -- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md) -- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md) -- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md) -- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md) -- [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) -- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md) -- [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/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) -- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md) -- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md) -- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md) -- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md) -- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md) -- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md) -- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md) -- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md) -- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md) +- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md) - [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/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) -- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md) - [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md) +- [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md) - [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) -- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/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) +- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/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) +- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md) +- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md) +- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md) +- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md) +- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md) +- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md) +- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md) +- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md) +- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md) +- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md) +- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md) +- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md) - [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/index.html.md) - [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md) - [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md) -- [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md) - [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md) - [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md) -- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md) - [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md) - [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md) - [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md) +- [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/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) - [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/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) -- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md) - [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md) +- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md) +- [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md) - [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md) -- [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) -- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md) -- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md) -- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md) -- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md) +- [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md) +- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) +- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md) +- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md) +- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/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) +- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md) +- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md) +- [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md) +- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md) +- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/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) +- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md) +- [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/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) +- [bulkCreateDeleteLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/bulkCreateDeleteLevelsWorkflow/index.html.md) +- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md) +- [createInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryItemsWorkflow/index.html.md) +- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md) +- [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md) +- [createInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryLevelsWorkflow/index.html.md) +- [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md) +- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md) +- [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md) - [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/index.html.md) -- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/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) - [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) -- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md) -- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md) -- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md) -- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/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) +- [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/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) - [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/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) -- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md) -- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md) -- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md) +- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md) - [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md) -- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md) -- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md) -- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md) +- [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/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) +- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md) +- [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md) +- [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/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) - [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) +- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md) +- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/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) -- [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/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) +- [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/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) - [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md) -- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md) - [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md) - [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md) -- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md) - [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md) +- [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md) - [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md) +- [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) -- [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md) - [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md) -- [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md) - [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/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) -- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md) -- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md) - [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md) +- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md) +- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md) - [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md) -- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md) - [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md) - [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md) -- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md) +- [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md) - [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md) -- [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md) +- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/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) - [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md) -- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md) - [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md) - [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md) +- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md) - [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md) - [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md) -- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md) - [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md) - [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md) +- [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/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) - [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md) - [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md) - [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md) -- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/index.html.md) - [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/index.html.md) -- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md) -- [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md) +- [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/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) - [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md) - [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md) +- [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/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) -- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md) - [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/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) +- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/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) -- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md) - [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md) +- [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md) - [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md) - [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/index.html.md) -- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md) -- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md) - [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md) +- [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md) +- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md) - [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md) -- [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md) - [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/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) - [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md) -- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md) - [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md) +- [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md) - [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md) - [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md) - [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md) - [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md) -- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md) - [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md) +- [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md) - [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md) -- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md) - [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/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) +- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md) - [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md) +- [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md) +- [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md) - [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md) - [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md) -- [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/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) +- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/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) -- [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md) - [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md) +- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md) - [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md) - [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md) -- [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/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) -- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md) - [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/index.html.md) -- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md) -- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md) +- [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md) - [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md) -- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md) +- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md) +- [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md) - [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md) - [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md) - [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md) +- [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md) - [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md) -- [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md) - [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md) -- [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md) - [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/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) -- [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/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) - [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md) +- [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md) - [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md) -- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md) +- [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md) - [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md) - [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md) +- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/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) - [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/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) +- [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) -- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/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) +- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md) +- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md) +- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md) +- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md) +- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/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) - [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md) +- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md) - [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md) -- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/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) +- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/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) +- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/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) -- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md) +- [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md) - [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md) - [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md) -- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md) +- [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md) - [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md) +- [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md) - [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md) -- [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/index.html.md) -- [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) - [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md) +- [deleteProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductOptionsWorkflow/index.html.md) - [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md) - [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md) - [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md) -- [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md) - [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md) -- [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/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) -- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/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) - [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md) -- [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md) +- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md) - [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md) -- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md) -- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md) -- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md) +- [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md) +- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md) - [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md) - [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md) -- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md) - [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md) - [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md) - [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md) -- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/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) -- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md) - [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md) +- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md) +- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md) - [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md) - [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md) -- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md) - [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/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) -- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md) -- [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md) -- [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md) -- [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/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) -- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md) -- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/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) -- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/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) +- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md) - [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md) -- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md) - [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) -- [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) -- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md) -- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md) -- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/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) +- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md) +- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md) +- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md) +- [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md) +- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md) +- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md) +- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/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) - [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md) -- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/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) -- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/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) - [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/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) +- [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md) +- [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md) +- [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md) +- [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md) +- [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md) +- [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md) +- [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md) +- [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md) +- [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md) +- [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md) ## Steps -- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/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) - [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md) -- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md) -- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md) -- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md) - [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md) -- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md) -- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md) -- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md) -- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md) -- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md) -- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md) -- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md) -- [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md) +- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md) - [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/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) +- [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/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) +- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md) - [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md) - [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md) -- [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md) -- [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) -- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md) +- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md) - [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md) -- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md) +- [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md) - [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md) -- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md) +- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md) - [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md) +- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/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) - [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md) -- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md) -- [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) - [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md) -- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md) -- [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md) +- [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md) +- [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md) +- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md) - [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md) +- [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) +- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md) +- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md) - [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) -- [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md) - [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/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) - [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md) +- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md) +- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md) - [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) -- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md) -- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md) -- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md) -- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md) +- [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) +- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/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) +- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md) +- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md) - [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md) - [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md) - [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md) - [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md) +- [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md) - [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md) -- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/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) - [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md) +- [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md) - [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md) - [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md) - [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md) - [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) -- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/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) +- [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md) - [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md) - [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md) -- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md) - [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md) -- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md) +- [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md) - [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md) - [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md) +- [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md) - [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md) - [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md) -- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md) - [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md) +- [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md) - [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md) +- [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) - [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md) -- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md) -- [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) -- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md) -- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md) -- [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) - [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) +- [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md) +- [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md) +- [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md) +- [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md) +- [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md) +- [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md) +- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md) +- [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md) +- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md) +- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md) +- [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md) - [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md) -- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md) - [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/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) +- [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md) - [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md) - [archiveOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/archiveOrdersStep/index.html.md) -- [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/index.html.md) - [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md) - [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) -- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md) - [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md) +- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md) - [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md) - [createCompleteReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCompleteReturnStep/index.html.md) +- [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/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) -- [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/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) +- [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md) +- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md) - [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) -- [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md) - [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md) - [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/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) -- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md) - [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md) +- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md) - [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md) -- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md) -- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md) - [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md) -- [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md) +- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md) - [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md) +- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/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) -- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md) - [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md) - [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md) +- [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md) +- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md) +- [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md) +- [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) +- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md) +- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md) +- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md) - [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md) +- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md) - [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md) - [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md) -- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md) - [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md) -- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md) -- [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md) -- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md) -- [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md) -- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md) -- [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md) -- [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md) +- [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) - [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) -- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md) - [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md) +- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md) - [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md) -- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md) - [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/index.html.md) -- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md) +- [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md) - [removePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/removePriceListPricesStep/index.html.md) -- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md) +- [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md) - [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) - [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) -- [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md) - [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md) - [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md) -- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/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) +- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md) - [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md) -- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md) - [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md) - [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md) +- [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md) - [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md) -- [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md) - [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) +- [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md) - [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/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) +- [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) - [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md) -- [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md) - [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md) -- [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/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) -- [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/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) +- [updateProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductOptionsStep/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) +- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md) - [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md) - [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/index.html.md) -- [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md) -- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md) -- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md) -- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md) -- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/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) - [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md) - [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md) -- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/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) +- [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) -- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md) - [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md) +- [removeCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeCampaignPromotionsStep/index.html.md) +- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/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) - [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md) +- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md) +- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md) +- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md) +- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/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) +- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md) - [associateLocationsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateLocationsWithSalesChannelsStep/index.html.md) - [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md) - [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md) - [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md) - [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md) +- [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md) - [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md) - [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md) - [updateSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateSalesChannelsStep/index.html.md) -- [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) - [listShippingOptionsForContextStep](https://docs.medusajs.com/references/medusa-workflows/steps/listShippingOptionsForContextStep/index.html.md) -- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md) - [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md) - [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md) -- [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) -- [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) -- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md) -- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md) -- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md) -- [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/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) +- [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) - [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md) - [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md) - [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md) +- [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md) +- [deleteTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRateRulesStep/index.html.md) +- [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) +- [deleteTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRegionsStep/index.html.md) +- [getItemTaxLinesStep](https://docs.medusajs.com/references/medusa-workflows/steps/getItemTaxLinesStep/index.html.md) +- [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md) +- [deleteTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteTaxRatesStep/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) +- [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md) - [createUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createUsersStep/index.html.md) -- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md) - [deleteUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteUsersStep/index.html.md) -- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md) +- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/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) # Medusa CLI Reference @@ -25425,67 +25798,6 @@ By default, the Medusa Admin is built to the `.medusa/server/public/admin` direc If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead. -# 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.| - - -# exec Command - Medusa CLI Reference - -Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md). - -```bash -npx medusa exec [file] [args...] -``` - -## Arguments - -|Argument|Description|Required| -|---|---|---|---|---| -|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes| -|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No| - - -# develop Command - Medusa CLI Reference - -Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. - -```bash -npx medusa develop -``` - -## Options - -|Option|Description|Default| -|---|---|---|---|---| -|\`-H \\`|Set host of the Medusa server.|\`localhost\`| -|\`-p \\`|Set port of the Medusa server.|\`9000\`| - - # db Commands - Medusa CLI Reference Commands starting with `db:` perform actions on the database. @@ -25606,6 +25918,22 @@ npx medusa db:sync-links |\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.| +# exec Command - Medusa CLI Reference + +Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md). + +```bash +npx medusa exec [file] [args...] +``` + +## Arguments + +|Argument|Description|Required| +|---|---|---|---|---| +|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes| +|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No| + + # 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. @@ -25667,12 +25995,12 @@ npx medusa plugin:build ``` -# start Command - Medusa CLI Reference +# develop Command - Medusa CLI Reference -Start the Medusa application in production. +Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. ```bash -npx medusa start +npx medusa develop ``` ## Options @@ -25683,6 +26011,35 @@ npx medusa start |\`-p \\`|Set port of the Medusa server.|\`9000\`| +# new Command - Medusa CLI Reference + +Create a new Medusa application. Unlike the `create-medusa-app` CLI tool, this command provides more flexibility for experienced Medusa developers in creating and configuring their project. + +```bash +medusa new [ []] +``` + +## Arguments + +|Argument|Description|Required|Default| +|---|---|---|---|---|---|---| +|\`dir\_name\`|The name of the directory to create the Medusa application in.|Yes|-| +|\`starter\_url\`|The name of the directory to create the Medusa application in.|No|\`https://github.com/medusajs/medusa-starter-default\`| + +## Options + +|Option|Description| +|---|---|---| +|\`-y\`|Skip all prompts, such as databaes prompts. A database might not be created if default PostgreSQL credentials don't work.| +|\`--skip-db\`|Skip database creation.| +|\`--skip-env\`|Skip populating | +|\`--db-user \\`|The database user to use for database setup.| +|\`--db-database \\`|The name of the database used for database setup.| +|\`--db-pass \\`|The database password to use for database setup.| +|\`--db-port \\`|The database port to use for database setup.| +|\`--db-host \\`|The database host to use for database setup.| + + # start-cluster Command - Medusa CLI Reference Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). @@ -25718,6 +26075,22 @@ npx medusa telemetry |\`--disable\`|Disable telemetry.| +# start Command - Medusa CLI Reference + +Start the Medusa application in production. + +```bash +npx medusa start +``` + +## Options + +|Option|Description|Default| +|---|---|---|---|---| +|\`-H \\`|Set host of the Medusa server.|\`localhost\`| +|\`-p \\`|Set port of the Medusa server.|\`9000\`| + + # user Command - Medusa CLI Reference Create a new admin user. @@ -25822,6 +26195,38 @@ By default, the Medusa Admin is built to the `.medusa/server/public/admin` direc If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead. +# exec Command - Medusa CLI Reference + +Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md). + +```bash +npx medusa exec [file] [args...] +``` + +## Arguments + +|Argument|Description|Required| +|---|---|---|---|---| +|\`file\`|The path to the TypeScript or JavaScript file holding the function to execute.|Yes| +|\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No| + + +# develop Command - Medusa CLI Reference + +Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. + +```bash +npx medusa develop +``` + +## Options + +|Option|Description|Default| +|---|---|---|---|---| +|\`-H \\`|Set host of the Medusa server.|\`localhost\`| +|\`-p \\`|Set port of the Medusa server.|\`9000\`| + + # db Commands - Medusa CLI Reference Commands starting with `db:` perform actions on the database. @@ -26032,22 +26437,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. @@ -26083,38 +26472,6 @@ npx medusa start-cluster |\`-p \\`|Set port of the Medusa server.|\`9000\`| -# develop Command - Medusa CLI Reference - -Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. - -```bash -npx medusa develop -``` - -## Options - -|Option|Description|Default| -|---|---|---|---|---| -|\`-H \\`|Set host of the Medusa server.|\`localhost\`| -|\`-p \\`|Set port of the Medusa server.|\`9000\`| - - -# telemetry Command - Medusa CLI Reference - -Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage. - -```bash -npx medusa telemetry -``` - -#### Options - -|Option|Description| -|---|---|---| -|\`--enable\`|Enable telemetry (default).| -|\`--disable\`|Disable telemetry.| - - # user Command - Medusa CLI Reference Create a new admin user. @@ -26134,6 +26491,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. @@ -26393,17 +26766,37 @@ Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-y - [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) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md) - [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md) -- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md) +- [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/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) +- [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) +- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/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) +- [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) +- [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) +- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md) +- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md) +- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md) +- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md) - [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) @@ -26414,106 +26807,83 @@ Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-y - [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) -- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md) +- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md) - [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) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md) - [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md) -- [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) - [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md) -- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md) +- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md) - [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md) - [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md) -- [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) -- [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/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) -- [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) -- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md) -- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md) -- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md) -- [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) -- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md) -- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md) -- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md) +- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md) - [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md) -- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md) -- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md) - [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md) -- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md) +- [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) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md) +- [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md) - [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/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) - [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md) -- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md) -- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md) -- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md) -- [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md) +- [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md) +- [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md) +- [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md) - [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) -- [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) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md) -- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md) - [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md) -- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md) -- [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md) -- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md) -- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md) -- [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md) +- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md) +- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md) +- [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md) - [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/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) +- [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) - [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) +- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md) - [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md) - [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md) - [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md) - [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md) -- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancel/index.html.md) - [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md) +- [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) -- [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) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md) +- [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md) - [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md) +- [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md) - [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md) - [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md) -- [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) -- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md) - [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md) -- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md) - [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md) - [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md) - [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md) @@ -26523,32 +26893,35 @@ Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-y - [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) - [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md) -- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md) - [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md) +- [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md) - [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/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/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md) - [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) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/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/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md) +- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md) - [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) -- [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) - [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/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) - [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md) -- [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/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) - [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md) - [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md) @@ -26556,140 +26929,139 @@ Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-y - [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md) - [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md) -- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md) +- [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md) - [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/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/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md) - [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md) - [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) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/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/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md) +- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md) -- [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) +- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md) - [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md) - [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md) - [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md) - [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md) - [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md) - [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md) -- [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) - [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) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md) -- [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/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md) +- [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) - [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) - [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md) +- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md) - [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md) -- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md) -- [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) -- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md) +- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md) - [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md) +- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md) +- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md) - [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) - [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) -- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md) -- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md) - [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) -- [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) - [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) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md) -- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md) -- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md) - [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md) -- [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) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md) +- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md) +- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/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) +- [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.retrieve/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md) - [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md) -- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/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) -- [create](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md) -- [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md) +- [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/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) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/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) +- [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) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.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) ## JS SDK Auth -- [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md) -- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md) -- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md) - [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md) +- [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md) +- [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md) - [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md) +- [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md) - [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md) - [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md) @@ -26697,12 +27069,12 @@ Learn more in the [Next.js documentation](https://nextjs.org/docs/app/building-y ## JS SDK Store - [cart](https://docs.medusajs.com/references/js-sdk/store/cart/index.html.md) -- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md) -- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md) -- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md) - [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md) -- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md) - [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md) +- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md) +- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md) +- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md) +- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md) - [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md) - [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md) @@ -26877,9 +27249,7 @@ module.exports = defineConfig({ - connection: (\`object\`) - - ssl: (\`object\`) Configure support for TLS/SSL connection - - - rejectUnauthorized: (\`false\`) Whether to fail connection if the server certificate is verified against the list of supplied CAs and the hostname and no match is found. + - ssl: (\`boolean\` \\| \`ConnectionOptions\`) Configure support for TLS/SSL connection ### redisUrl @@ -27200,6 +27570,27 @@ module.exports = defineConfig({ Configure the Vite configuration for the admin dashboard. This function receives the default Vite configuration and returns the modified configuration. The default value is `undefined`. +Learn about configurations you can pass to Vite in [Vite's documentation](https://vite.dev/config/). + +#### Example + +For example, if you're using a third-party library that isn't ESM-compatible, add it to Vite's `optimizeDeps` configuration: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + vite: () => { + return { + optimizeDeps: { + include: ["qs"], + }, + }; + }, + }, + // ... +}) +``` + *** ## plugins @@ -27526,231 +27917,6 @@ export default ProductWidget This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. -# Action Menu - Admin Components - -The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon. - -![Example of an action menu in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728291319/Medusa%20Resources/action-menu_jnus6k.png) - -To create a component that shows this menu in your customizations, create the file `src/admin/components/action-menu.tsx` with the following content: - -```tsx title="src/admin/components/action-menu.tsx" -import { - DropdownMenu, - IconButton, - clx, -} from "@medusajs/ui" -import { EllipsisHorizontal } from "@medusajs/icons" -import { Link } from "react-router-dom" - -export type Action = { - icon: React.ReactNode - label: string - disabled?: boolean -} & ( - | { - to: string - onClick?: never - } - | { - onClick: () => void - to?: never - } -) - -export type ActionGroup = { - actions: Action[] -} - -export type ActionMenuProps = { - groups: ActionGroup[] -} - -export const ActionMenu = ({ groups }: ActionMenuProps) => { - return ( - - - - - - - - {groups.map((group, index) => { - if (!group.actions.length) { - return null - } - - const isLast = index === groups.length - 1 - - return ( - - {group.actions.map((action, index) => { - if (action.onClick) { - return ( - { - e.stopPropagation() - action.onClick() - }} - className={clx( - "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", - { - "[&_svg]:text-ui-fg-disabled": action.disabled, - } - )} - > - {action.icon} - {action.label} - - ) - } - - return ( -
- - e.stopPropagation()}> - {action.icon} - {action.label} - - -
- ) - })} - {!isLast && } -
- ) - })} -
-
- ) -} -``` - -The `ActionMenu` component shows a three-dots icon (or `EllipsisHorizontal`) from the [Medusa Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md) in a button. - -When the button is clicked, a dropdown menu is shown with the actions passed in the props. - -The component accepts the following props: - -- groups: (\`object\[]\`) Groups of actions to be shown in the dropdown. Each group is separated by a divider. - - - actions: (\`object\[]\`) Actions in the group. - - - icon: (\`React.ReactNode\`) - - - label: (\`string\`) The action's text. - - - disabled: (\`boolean\`) Whether the action is shown as disabled. - - - \`to\`: (\`string\`) The link to take the user to when they click the action. This is required if \`onClick\` isn't provided. - - - \`onClick\`: (\`() => void\`) The function to execute when the action is clicked. This is required if \`to\` isn't provided. - -*** - -## Example - -Use the `ActionMenu` 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 { Pencil } from "@medusajs/icons" -import { Container } from "../components/container" -import { ActionMenu } from "../components/action-menu" - -const ProductWidget = () => { - return ( - - , - label: "Edit", - onClick: () => { - alert("You clicked the edit action!") - }, - }, - ], - }, - ]} /> - - ) -} - -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. - -### Use in Header - -You can also use the action menu in the [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) component as part of its actions. - -For example: - -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Pencil } from "@medusajs/icons" -import { Container } from "../components/container" -import { Header } from "../components/header" - -const ProductWidget = () => { - return ( - -
, - label: "Edit", - onClick: () => { - alert("You clicked the edit action!") - }, - }, - ], - }, - ], - }, - }, - ]} - /> - - ) -} - -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) - -export default ProductWidget -``` - - # Forms - Admin Components The Medusa Admin has two types of forms: @@ -28789,6 +28955,231 @@ export default CustomPage ``` +# Action Menu - Admin Components + +The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon. + +![Example of an action menu in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728291319/Medusa%20Resources/action-menu_jnus6k.png) + +To create a component that shows this menu in your customizations, create the file `src/admin/components/action-menu.tsx` with the following content: + +```tsx title="src/admin/components/action-menu.tsx" +import { + DropdownMenu, + IconButton, + clx, +} from "@medusajs/ui" +import { EllipsisHorizontal } from "@medusajs/icons" +import { Link } from "react-router-dom" + +export type Action = { + icon: React.ReactNode + label: string + disabled?: boolean +} & ( + | { + to: string + onClick?: never + } + | { + onClick: () => void + to?: never + } +) + +export type ActionGroup = { + actions: Action[] +} + +export type ActionMenuProps = { + groups: ActionGroup[] +} + +export const ActionMenu = ({ groups }: ActionMenuProps) => { + return ( + + + + + + + + {groups.map((group, index) => { + if (!group.actions.length) { + return null + } + + const isLast = index === groups.length - 1 + + return ( + + {group.actions.map((action, index) => { + if (action.onClick) { + return ( + { + e.stopPropagation() + action.onClick() + }} + className={clx( + "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", + { + "[&_svg]:text-ui-fg-disabled": action.disabled, + } + )} + > + {action.icon} + {action.label} + + ) + } + + return ( +
+ + e.stopPropagation()}> + {action.icon} + {action.label} + + +
+ ) + })} + {!isLast && } +
+ ) + })} +
+
+ ) +} +``` + +The `ActionMenu` component shows a three-dots icon (or `EllipsisHorizontal`) from the [Medusa Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md) in a button. + +When the button is clicked, a dropdown menu is shown with the actions passed in the props. + +The component accepts the following props: + +- groups: (\`object\[]\`) Groups of actions to be shown in the dropdown. Each group is separated by a divider. + + - actions: (\`object\[]\`) Actions in the group. + + - icon: (\`React.ReactNode\`) + + - label: (\`string\`) The action's text. + + - disabled: (\`boolean\`) Whether the action is shown as disabled. + + - \`to\`: (\`string\`) The link to take the user to when they click the action. This is required if \`onClick\` isn't provided. + + - \`onClick\`: (\`() => void\`) The function to execute when the action is clicked. This is required if \`to\` isn't provided. + +*** + +## Example + +Use the `ActionMenu` 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 { Pencil } from "@medusajs/icons" +import { Container } from "../components/container" +import { ActionMenu } from "../components/action-menu" + +const ProductWidget = () => { + return ( + + , + label: "Edit", + onClick: () => { + alert("You clicked the edit action!") + }, + }, + ], + }, + ]} /> + + ) +} + +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. + +### Use in Header + +You can also use the action menu in the [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) component as part of its actions. + +For example: + +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Pencil } from "@medusajs/icons" +import { Container } from "../components/container" +import { Header } from "../components/header" + +const ProductWidget = () => { + return ( + +
, + label: "Edit", + onClick: () => { + alert("You clicked the edit action!") + }, + }, + ], + }, + ], + }, + }, + ]} + /> + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + + # 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. @@ -28935,234 +29326,6 @@ 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. - -![Example of a JSON section in the admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295129/Medusa%20Resources/json_dtbsgm.png) - -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" }`. - - # Section Row - Admin Components The Medusa Admin often shows information in rows of label-values, such as when showing a product's details. @@ -29547,6 +29710,234 @@ 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. +# JSON View - Admin Components + +Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format. + +![Example of a JSON section in the admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295129/Medusa%20Resources/json_dtbsgm.png) + +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" }`. + + # 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. @@ -29730,133 +30121,6 @@ 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). - - -# restore Method - Service Factory Reference - -This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md). - -## Restore One Record - -```ts -const restoredPosts = await postModuleService.restorePosts("123") -``` - -### Parameters - -To restore one 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 restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: ["123"], -} -``` - -*** - -## Restore Multiple Records - -```ts -const restoredPosts = await postModuleService.restorePosts([ - "123", - "321", -]) -``` - -### Parameters - -To restore 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 restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: [ - "123", - "321", - ], -} -``` - -*** - -## Restore Records Matching Filters - -```ts -const restoredPosts = await postModuleService.restorePosts({ - name: "My Post", -}) -``` - -### Parameters - -To restore records matching a set of filters, pass an object of fitlers as a parameter of the method. - -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 restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: [ - "123", - ], -} -``` - - # listAndCount Method - Service Factory Reference This method retrieves a list of records with the total count. @@ -29993,6 +30257,46 @@ The method returns an array with two items: 2. The second is the total count of records. +# 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). + + # retrieve Method - Service Factory Reference This method retrieves one record of the data model by its ID. @@ -30137,6 +30441,93 @@ deletedPosts = { ``` +# restore Method - Service Factory Reference + +This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md). + +## Restore One Record + +```ts +const restoredPosts = await postModuleService.restorePosts("123") +``` + +### Parameters + +To restore one 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 restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: ["123"], +} +``` + +*** + +## Restore Multiple Records + +```ts +const restoredPosts = await postModuleService.restorePosts([ + "123", + "321", +]) +``` + +### Parameters + +To restore 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 restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: [ + "123", + "321", + ], +} +``` + +*** + +## Restore Records Matching Filters + +```ts +const restoredPosts = await postModuleService.restorePosts({ + name: "My Post", +}) +``` + +### Parameters + +To restore records matching a set of filters, pass an object of fitlers as a parameter of the method. + +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 restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: [ + "123", + ], +} +``` + + # 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. @@ -30843,6 +31234,168 @@ 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. @@ -37054,165 +37607,3 @@ 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. diff --git a/www/apps/resources/app/commerce-modules/fulfillment/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/fulfillment/links-to-other-modules/page.mdx index cb6b1fe950..97cf30cc66 100644 --- a/www/apps/resources/app/commerce-modules/fulfillment/links-to-other-modules/page.mdx +++ b/www/apps/resources/app/commerce-modules/fulfillment/links-to-other-modules/page.mdx @@ -15,6 +15,7 @@ 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). @@ -216,6 +217,101 @@ createRemoteLinkStep({ --- +## 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](!docs!/learn/fundamentals/module-links/query), pass `products.*` in `fields`: + + + + +```ts +const { data: shippingProfiles } = await query.graph({ + entity: "shipping_profile", + fields: [ + "products.*", + ], +}) + +// shippingProfiles.products +``` + + + + +```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](!docs!/learn/fundamentals/module-links/link): + + + + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.create({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", + }, +}) +``` + + + + +```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. diff --git a/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx index 9da14e6737..8584b4f55b 100644 --- a/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx +++ b/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx @@ -19,6 +19,7 @@ Read-only links are used to query data across modules, but the relations aren't - [`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). @@ -80,6 +81,101 @@ const { data: variants } = useQueryGraphStep({ --- +## 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](!docs!/learn/fundamentals/module-links/query), pass `shipping_profile.*` in `fields`: + + + + +```ts +const { data: products } = await query.graph({ + entity: "product", + fields: [ + "shipping_profile.*", + ], +}) + +// products.shipping_profile +``` + + + + +```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](!docs!/learn/fundamentals/module-links/link): + + + + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.create({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", + }, +}) +``` + + + + +```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. diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 28603704ae..eaf13645e6 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -2114,11 +2114,11 @@ export const generatedEditDates = { "app/commerce-modules/cart/extend/page.mdx": "2024-12-25T12:48:59.149Z", "app/commerce-modules/cart/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.593Z", "app/commerce-modules/customer/extend/page.mdx": "2024-12-25T15:54:37.789Z", - "app/commerce-modules/fulfillment/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.593Z", + "app/commerce-modules/fulfillment/links-to-other-modules/page.mdx": "2025-02-11T12:10:04.901Z", "app/commerce-modules/inventory/links-to-other-modules/page.mdx": "2025-02-03T12:37:33.622Z", "app/commerce-modules/pricing/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.607Z", "app/commerce-modules/product/extend/page.mdx": "2024-12-11T09:07:25.252Z", - "app/commerce-modules/product/links-to-other-modules/page.mdx": "2025-02-03T12:37:18.919Z", + "app/commerce-modules/product/links-to-other-modules/page.mdx": "2025-02-11T12:09:46.420Z", "app/commerce-modules/promotion/extend/page.mdx": "2024-12-11T09:07:24.137Z", "app/commerce-modules/promotion/links-to-other-modules/page.mdx": "2025-01-06T11:19:35.608Z", "app/commerce-modules/order/edit/page.mdx": "2024-10-09T08:50:05.334Z",