From c6bfad14d882993725566b8d647f985464395c3f Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Mon, 3 Apr 2023 14:50:59 +0300 Subject: [PATCH] docs: add documentation for v1.8 (#3669) --- docs/content/admin/quickstart.mdx | 243 +- docs/content/create-medusa-app.mdx | 49 +- .../deployments/admin/deploying-on-netlify.md | 319 -- .../deployments/admin/deploying-on-vercel.md | 197 ++ .../server/deploying-on-digital-ocean.md | 22 +- .../server/deploying-on-heroku.mdx | 20 +- .../deployments/server/deploying-on-qovery.md | 26 +- .../server/deploying-on-railway.md | 15 +- .../development/backend/configurations.md | 102 +- docs/content/development/backend/install.mdx | 2 +- .../backend/prepare-environment.mdx | 69 +- .../content/development/batch-jobs/create.mdx | 6 +- .../batch-jobs/customize-import.md | 6 +- docs/content/development/batch-jobs/index.mdx | 2 +- docs/content/development/cache/create.md | 265 ++ .../development/cache/modules/in-memory.md | 70 + .../development/cache/modules/redis.md | 92 + docs/content/development/cache/overview.mdx | 65 + .../development/endpoints/add-middleware.md | 177 +- .../endpoints/example-logged-in-user.md | 193 ++ docs/content/development/entities/create.md | 87 +- .../development/entities/extend-entity.md | 158 + .../development/entities/extend-repository.md | 131 + .../development/entities/migrations/create.md | 47 +- .../content/development/entities/overview.mdx | 22 +- .../development/events/create-module.md | 260 ++ .../development/events/create-subscriber.md | 29 +- .../content/development/events/events-list.md | 385 ++- docs/content/development/events/index.md | 227 -- docs/content/development/events/index.mdx | 131 + .../development/events/modules/local.md | 68 + .../development/events/modules/redis.md | 95 + .../development/events/subscribers.mdx | 10 - .../fundamentals/architecture-overview.md | 2 +- .../fundamentals/dependency-injection.md | 159 +- docs/content/development/modules/create.mdx | 305 ++ docs/content/development/modules/overview.mdx | 47 + docs/content/development/modules/publish.md | 198 ++ .../create-notification-provider.md | 6 +- docs/content/development/plugins/overview.mdx | 2 +- .../development/scheduled-jobs/create.md | 6 +- .../development/scheduled-jobs/overview.mdx | 2 +- .../development/services/create-service.md | 33 +- .../development/services/extend-service.md | 86 + .../content/development/services/overview.mdx | 28 +- docs/content/homepage.mdx | 40 +- docs/content/medusa-react/overview.md | 2 +- .../backend/add-payment-provider.md | 561 ++-- .../modules/carts-and-checkout/overview.mdx | 6 +- .../modules/carts-and-checkout/payment.md | 64 +- .../storefront/implement-checkout-flow.mdx | 34 +- .../customers/admin/manage-customers.mdx | 6 +- .../backend/send-gift-card-to-customer.md | 6 +- .../modules/multiwarehouse/install-modules.md | 87 + .../multiwarehouse/inventory-module.md | 102 + .../modules/multiwarehouse/overview.mdx | 168 ++ .../multiwarehouse/stock-location-module.md | 63 + .../backend/handle-order-claim-event.md | 6 +- .../orders/storefront/handle-order-edits.mdx | 8 +- docs/content/modules/overview.mdx | 25 +- .../price-lists/admin/import-prices.mdx | 6 +- .../products/admin/import-products.mdx | 6 +- .../products/admin/manage-categories.mdx | 678 +++++ docs/content/modules/products/categories.md | 94 + docs/content/modules/products/overview.mdx | 30 +- .../admin/manage-regions.mdx | 11 +- .../regions-and-currencies/overview.mdx | 4 +- .../modules/regions-and-currencies/regions.md | 10 +- docs/content/plugins/analytics/segment.md | 4 +- docs/content/plugins/cms/contentful/index.md | 2 +- .../plugins/notifications/sendgrid.mdx | 4 +- docs/content/plugins/notifications/slack.md | 10 +- .../plugins/notifications/twilio-sms.md | 2 +- docs/content/plugins/payment/klarna.md | 6 + docs/content/plugins/payment/paypal.md | 46 +- docs/content/plugins/payment/stripe.md | 20 +- docs/content/plugins/search/algolia.md | 114 +- docs/content/plugins/search/meilisearch.md | 110 +- .../starters/nextjs-medusa-starter.mdx | 2 +- .../awilix-resolution-error.md | 78 + .../missing-payment-providers.md | 6 +- docs/content/troubleshooting/redis-events.md | 6 + docs/content/upgrade-guides/admin/1-0-0.md | 48 + docs/content/upgrade-guides/index.mdx | 4 +- .../upgrade-guides/medusa-core/1-8-0.md | 89 + .../upgrade-guides/plugins/_category_.json | 5 + .../upgrade-guides/plugins/algolia/1-0-0.md | 71 + .../plugins/algolia/_category_.json | 6 + .../plugins/meilisearch/1-0-0.md | 74 + .../plugins/meilisearch/_category_.json | 6 + docs/content/user-guide.mdx | 2 +- .../user-guide/customers/_category_.json | 2 +- .../user-guide/multiwarehouse/_category_.json | 9 + .../user-guide/multiwarehouse/index.md | 32 + .../user-guide/multiwarehouse/inventory.mdx | 30 + .../user-guide/multiwarehouse/locations.mdx | 80 + docs/content/user-guide/orders/claims.mdx | 7 + docs/content/user-guide/orders/exchange.mdx | 8 +- .../user-guide/orders/fulfillments.mdx | 7 +- docs/content/user-guide/orders/manage.mdx | 50 + docs/content/user-guide/orders/returns.mdx | 7 + .../user-guide/products/_category_.json | 2 +- .../user-guide/products/categories.mdx | 78 + .../user-guide/products/collections.mdx | 2 +- docs/content/user-guide/products/export.mdx | 2 +- docs/content/user-guide/products/import.mdx | 2 +- docs/content/user-guide/products/index.mdx | 5 +- docs/content/user-guide/products/manage.mdx | 43 +- packages/medusa-payment-paypal/README.md | 2 - packages/medusa-payment-stripe/README.md | 2 - packages/medusa-plugin-algolia/README.md | 26 +- packages/medusa-plugin-meilisearch/README.md | 36 +- www/docs/announcement.json | 2 +- www/docs/sidebars.js | 2564 +++++++++-------- www/docs/src/components/Badge/index.js | 24 +- .../src/components/Badge/styles.module.css | 12 + www/docs/src/components/Feedback/index.css | 3 + www/docs/src/css/_docspage.css | 20 + www/docs/src/css/_medusa.css | 20 + .../src/theme/Icon/BuildingSolid/index.tsx | 12 + www/docs/src/theme/Icon/SwatchSolid/index.tsx | 14 + www/docs/src/theme/Icon/index.ts | 4 + www/docs/vercel.json | 4 + 123 files changed, 7610 insertions(+), 2697 deletions(-) delete mode 100644 docs/content/deployments/admin/deploying-on-netlify.md create mode 100644 docs/content/deployments/admin/deploying-on-vercel.md create mode 100644 docs/content/development/cache/create.md create mode 100644 docs/content/development/cache/modules/in-memory.md create mode 100644 docs/content/development/cache/modules/redis.md create mode 100644 docs/content/development/cache/overview.mdx create mode 100644 docs/content/development/endpoints/example-logged-in-user.md create mode 100644 docs/content/development/entities/extend-entity.md create mode 100644 docs/content/development/entities/extend-repository.md create mode 100644 docs/content/development/events/create-module.md delete mode 100644 docs/content/development/events/index.md create mode 100644 docs/content/development/events/index.mdx create mode 100644 docs/content/development/events/modules/local.md create mode 100644 docs/content/development/events/modules/redis.md create mode 100644 docs/content/development/modules/create.mdx create mode 100644 docs/content/development/modules/overview.mdx create mode 100644 docs/content/development/modules/publish.md create mode 100644 docs/content/development/services/extend-service.md create mode 100644 docs/content/modules/multiwarehouse/install-modules.md create mode 100644 docs/content/modules/multiwarehouse/inventory-module.md create mode 100644 docs/content/modules/multiwarehouse/overview.mdx create mode 100644 docs/content/modules/multiwarehouse/stock-location-module.md create mode 100644 docs/content/modules/products/admin/manage-categories.mdx create mode 100644 docs/content/modules/products/categories.md create mode 100644 docs/content/troubleshooting/awilix-resolution-error.md create mode 100644 docs/content/upgrade-guides/admin/1-0-0.md create mode 100644 docs/content/upgrade-guides/medusa-core/1-8-0.md create mode 100644 docs/content/upgrade-guides/plugins/_category_.json create mode 100644 docs/content/upgrade-guides/plugins/algolia/1-0-0.md create mode 100644 docs/content/upgrade-guides/plugins/algolia/_category_.json create mode 100644 docs/content/upgrade-guides/plugins/meilisearch/1-0-0.md create mode 100644 docs/content/upgrade-guides/plugins/meilisearch/_category_.json create mode 100644 docs/content/user-guide/multiwarehouse/_category_.json create mode 100644 docs/content/user-guide/multiwarehouse/index.md create mode 100644 docs/content/user-guide/multiwarehouse/inventory.mdx create mode 100644 docs/content/user-guide/multiwarehouse/locations.mdx create mode 100644 docs/content/user-guide/products/categories.mdx create mode 100644 www/docs/src/theme/Icon/BuildingSolid/index.tsx create mode 100644 www/docs/src/theme/Icon/SwatchSolid/index.tsx diff --git a/docs/content/admin/quickstart.mdx b/docs/content/admin/quickstart.mdx index 0bf2507023..6e87c7f1b3 100644 --- a/docs/content/admin/quickstart.mdx +++ b/docs/content/admin/quickstart.mdx @@ -1,29 +1,40 @@ --- -description: 'Learn how to install the Medusa admin. The Medusa admin gives merchants an easy-to-use interface to manage their data such as orders, products, regions, and more.' +description: "Learn how to install Medusa's admin dashboard. The admin dashboard gives merchants an easy-to-use interface to manage their data such as orders, products, regions, and more." addHowToData: true --- import Feedback from '@site/src/components/Feedback'; -# Medusa Admin Quickstart +# Admin Dashboard Quickstart -This document will guide you through setting up the Medusa admin in minutes, as well as some of its features. +This document will guide you through setting up the admin dashboard in the Medusa backend. + +:::note + +The admin dashboard is now shipped as an NPM package, and the previous GitHub repository has been deprecated. + +::: + +## Overview + +The admin dashboard is installed on the Medusa backend. Setting it up depends on how you intend to use it: + +1. [Served alongside the Medusa backend](#option-1-install-and-serve-admin-with-the-backend): with this approach, the admin dashboard starts when you start the Medusa backend. This also means you can later deploy the Medusa backend along with the admin dashboard on the same hosting. +2. [Served separately from the Medusa backend](#option-2-install-and-serve-admin-separately): with this approach, the admin dashboard starts separately from the Medusa backend. You still need the Medusa backend to be running as the admin uses its APIs. This is useful if you intend to later deploy the admin dashboard on a different hosting than the Medusa Backend, such as using Vercel. + +This guide will explain the steps and configurations required for both approaches. + +--- ## Prerequisites ### Medusa Backend -The Medusa admin is connected to the Medusa backend. So, make sure to install the Medusa backend first before proceeding with the admin. You can check out the [quickstart guide to install the Medusa backend](../development/backend/install.mdx). - -:::tip - -If you’re not very familiar with Medusa’s architecture, you can learn more about it in the [Architecture Overview](../development/fundamentals/architecture-overview.md). - -::: +As the admin dashboard is a plugin installed on the Medusa Backend, you must have a Medusa Backend installed first. You can learn how to install it in [this documentation](../development/backend/install.mdx). ### Node.js -As Medusa Admin uses [Vite 3](https://vitejs.dev/guide/#scaffolding-your-first-vite-project), it requires versions 14.8+ or 16+. You can check which version of Node you have by running the following command: +The Admin uses [Vite v4.1.4](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) which requires v14.8+ or v16+ of Node.js. You can check which version of Node you have by running the following command: ```bash noReport node -v @@ -33,37 +44,68 @@ You can install Node from the [official website](https://nodejs.org/en/). --- -## Instant Deployment to Netlify +## Option 1: Install and Serve Admin with the Backend -Instead of manually following this guide to install then later deploy the Medusa Admin, you can deploy the Medusa Admin to Netlify with this button: +This section explains how to install the admin to be served with the Medusa Backend and later deployed together. - - Deploy to Netlify - +:::note ---- - -## Install the Admin - -:::tip - -It is recommended to use [Yarn](https://yarnpkg.com/getting-started/install) for the installation process as it's much faster than using NPM. +If you decide later to serve the admin for development separately or deploy it on a different hosting, you can go back and follow the steps in [Option 2](#option-2-install-and-serve-admin-serparately). ::: -Start by cloning the [Admin GitHub repository](https://github.com/medusajs/admin) and changing to the cloned directory: +### Step 1: Install the Package -```bash -git clone https://github.com/medusajs/admin medusa-admin -cd medusa-admin -``` - -Then, install the dependencies: +In the directory of your Medusa backend, run the following command to install admin dashboard: ```bash npm2yarn -npm install +npm install @medusajs/admin ``` +### Step 2: Add Admin to Medusa Configurations + +In `medusa-config.js`, add the admin plugin into the array of `plugins`: + +```js title=medusa-config.js +const plugins = [ + // ... + { + resolve: "@medusajs/admin", + /** @type {import('@medusajs/admin').PluginOptions} */ + options: { + // ... + }, + }, +] +``` + +The plugin accepts the following options: + +1. `serve`: (default: `true`) a boolean indicating whether to serve the admin dashboard when the Medusa backend starts. If set to `false`, you can serve the admin dashboard using the [dev command](#dev-command-options). +2. `path`: (default: `app`) a string indicating the path the admin server should run on. It shouldn't be prefixed or suffixed with a slash `/`, and it can't be one of the reserved paths: "admin" and "store". +3. `outDir`: Optional path for where to output the admin build files. +4. `autoRebuild`: (default: `false`) a boolean indicating whether the admin UI should be rebuilt if there are any changes or if a missing build is detected when the backend starts. If not set, you must [manually build the admin dashboard](#build-command-options). + +### Optional: Manually Building Admin Dashboard + +If you have `autoRebuild` disabled, you must build your admin dashboard before starting the Medusa backend. Refer to the [build command](#build-command-options) for more details. + +### Step 3: Test the Admin Dashboard + +:::tip + +If you disabled the `serve` option, you need to run the admin dashboard separately using the [dev command](#dev-command-options) + +::: + +You can test the admin dashboard by running the following command in the directory of the Medusa backend: + +```bash npm2yarn +npm run start +``` + +This starts the Medusa Backend and the admin dashboard. By default, the admin will be available on the URL `localhost:9000/app`. If you set the `path` option, then the admin will be available on `localhost:9000/` with `` being the value of the `path` option. + -:::tip +--- -If you run into errors during the installation, check out [this troubleshooting guide](../troubleshooting/common-installation-errors.mdx). +## Option 2: Install and Serve Admin Separately + +This section explains how to install the admin dashboard using approach 2, which allows you to serve and later deploy the admin separately. + +:::note + +If you decide later to serve and deploy the admin alongside the server, you can go back and follow the steps in [Option 1](#option-1-install-and-serve-admin-with-the-backend). ::: +### Step 1: Install the Package + +In the directory of your Medusa backend, run the following command to install admin dashboard: + +```bash npm2yarn +npm install @medusajs/admin --save-dev +``` + +### Step 2: Add Scripts to Package.json + +Add the following scripts to `package.json` in the directory of the Medusa backend: + +```json title=package.json +{ + "scripts": { + // other scripts... + "build:admin": "medusa-admin build", + "dev:admin": "medusa-admin dev" + } +} +``` + +Where: + +- `build:admin`: Used to manually create a build of the admin. In this approach, it's useful with the `--deployment` option to build the admin for deployment. You can learn more about all available options in [this section](#build-command-options). +- `dev:admin`: Used to run the development server of the admin. You can learn about other available options for this command in [this section](#dev-command-options). + +### Step 3: Start Admin in Development + +Make sure to run the Medusa backend first. Then, in the root directory of the backend, run the following command to start the admin development server: + +```bash npm2yarn +npm run dev:admin +``` + +This runs the admin dashboard on `localhost:7001`. + + + --- -## Test it Out - -Before running your Medusa admin, make sure that your Medusa backend is running. - -:::tip - -To run your Medusa backend, go to the directory holding the backend and run: - -```bash npm2yarn -npm run start -``` - -::: - -Then, in the directory holding your Medusa admin, run the following to start the development server: - -```bash npm2yarn -npm run start -``` - -By default, the admin runs on port 7000. So, in your browser, go to `localhost:7000` to view your admin. - -![Admin Log In](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001604/Medusa%20Docs/Screenshots/XYqMCo9_hq1fsv.png) - -Use your Medusa admin’s user credentials to log in. - -### Demo Credentials +## Demo Credentials If you installed the demo data when you installed the Medusa backend by using the `--seed` option or running: @@ -135,44 +201,51 @@ This will create a new user that you can use to log into your admin panel. --- -## Changing the Default Port +## Build Command Options -The default port is set in `package.json` in the `dev` script: +The `build` command in the admin CLI allows you to manually build the admin dashboard. If you intend to use it, you should typically add it to the `package.json` of the Medusa backend: -```json -"dev": "vite --port 7000", +```json title=package.json +{ + "scripts": { + // other scripts... + "build:admin": "medusa-admin build" + } +} ``` -If you wish to change the port you can simply change the `7000` to your desired port. +You can add the following options to the `medusa-admin build` command: -However, if you change your Medusa admin port, you need to change it in your Medusa backend. The Medusa backend has the Medusa admin and store URLs set in the configurations to avoid Cross-Origin Resource Sharing (CORS) issues. +- `--deployment`: a boolean value indicating that the build should be ready for deployment. When this option is added, options are not loaded from `medusa-config.js` anymore, and it means the admin will be built to be hosted on an external host. For example, `medusa-admin build --deployment`. +- `--backend` or `-b`: a string specifying the URL of the Medusa backend. This can be useful with the `--deployment` option. The default here is the value of the environment variable `MEDUSA_BACKEND_URL`. For example, `medusa-admin build --deployment --backend example.com` +- `--out-dir` or `-o`: a string specifying a custom path to output the build files to. By default, it will be the `build` directory. For example, `medusa-admin --deployment --out-dir public`. +- `--include` or `-i`: a list of strings of paths to files you want to include in the build output. It can be useful if you want to inject files that are relevant to your external hosting, such as adding a `200.html` file that is needed for redirects on Surge. For example, `medusa-admin --deployment --include 200.html` +- `--include-dist` or `-d`: a string specifying the path to copy the files specified in `--include` to. By default, the files are coopied to the root of the build directory. You can use this option to change that. For example, `medusa-admin --deployment --include 200.html --include-dist static`. -To change the URL of the Medusa admin in the backend, add a new environment variable `ADMIN_CORS` or modify it if you already have it to your Admin URL: +--- -```bash -ADMIN_CORS= +## Dev Command Options + +The `dev` command in the admin CLI allows you to run the admin dashboard in development separately from the Medusa backend. If you intend to use it, you should typically add it to the `package.json` of the Medusa backend: + +```json title=package.json +{ + "scripts": { + // other scripts... + "dev:admin": "medusa-admin dev" + } +} ``` -Make sure to replace `` with your URL. +You can add the following options to the `medusa-admin dev` command: -:::info - -For more details about the Admin CORS configuration, check out the [Configure your Backend documentation](../development/backend/configurations.md#admin-cors). - -::: +- `--backend` or `-b`: a string specifying the URL of the Medusa backend. By default, it's the value of the environment variable `MEDUSA_BACKEND_URL`. For example, `medusa-admin dev --backend example.com`. +- `--port` or `-p`: the port to run the admin on. By default, it's `7001`. For example, `medusa-admin dev --port 8000`. --- ## Admin User Guide -Medusa admin provides a lot of ecommerce features including managing Return Merchandise Authorization (RMA) flows, store settings, products, orders, and much more. +The admin dashboard provides a lot of ecommerce features including managing Return Merchandise Authorization (RMA) flows, store settings, products, orders, and much more. -You can learn more about Medusa admin and its features in the [User Guide](../user-guide.mdx). - ---- - -## See Also - -- [Customize Medusa Admin](./development.md) -- Install the [Next.js](../starters/nextjs-medusa-starter.mdx) storefront starter. -- [Use `create-medusa-app` to install all of Medusa’s 3 components.](../create-medusa-app.mdx) +You can learn more about the admin dashboard and its features in the [User Guide](../user-guide.mdx). diff --git a/docs/content/create-medusa-app.mdx b/docs/content/create-medusa-app.mdx index 716d0df120..88c1fccd55 100644 --- a/docs/content/create-medusa-app.mdx +++ b/docs/content/create-medusa-app.mdx @@ -9,21 +9,17 @@ import Feedback from '@site/src/components/Feedback'; # Install Medusa with create-medusa-app -In this document, you’ll learn how to use `create-medusa-app` to create a Medusa project with the three main components of Medusa. +In this document, you’ll learn how to use create-medusa-app to set up a Medusa backend and an optional storefront. ## Overview -Medusa is composed of three different components: the headless backend, the storefront, and the admin dashboard. +Medusa is a toolkit for developers to create digital commerce applications. In its simplest form, Medusa is a Node.js backend with the core API, plugins, and modules installed through npm. -Medusa provides the necessary tools and resources to set up the three components separately. This ensures that developers have full freedom to choose their tech stack, as they can choose any framework for the storefront and admin dashboard. - -However, if you’re interested in using Medusa’s starters for the three components, you can make use of the `create-medusa-app` command instead of creating each separately. - -When you run the `create-medusa-app` command, you’ll install a Medusa backend, a Medusa admin, and optionally a storefront at the same time. +`create-medusa-app` is a command that facilitates creating a Medusa ecosystem. It installs the Medusa backend and allows you to optionally install a Medusa storefront. The admin dashboard is installed as part of the Medusa backend. :::note -If you instead want to quickly install and setup only a Medusa backend, follow [this quickstart guide](./development/backend/install.mdx). +If you only want to set up a Medusa backend, follow [this quickstart guide](./development/backend/install.mdx) instead. ::: @@ -43,7 +39,7 @@ You can install Node from the [official website](https://nodejs.org/en/). ### Git -Git is required for this setup. You can find instructions on how to install it from the [Set up your dev environment documentation](./development/backend/prepare-environment.mdx#git). +Git is required for this setup. You can find instructions on how to install it from the [Prepare Environment documentation](./development/backend/prepare-environment.mdx#git). --- @@ -70,14 +66,16 @@ In your terminal, run the following command: -### Project Directory Name +### Step 1: Specify Project Directory Name You’ll then be asked to enter the name of the directory you want the project to be installed in. You can either leave the default value `my-medusa-store` or enter a new name. -### Choose Medusa Backend Starter +### Step 2: Choose Medusa Backend Starter Next, you’ll be asked to choose the Medusa backend starter. The Medusa Backend is created from a starter template. By default, it is created from the `medusa-starter-default` template. +The `medusa-starter-default` includes the admin plugin, which allows you to access the admin dashboard. If you choose a different backend starter that doesn't have the admin plugin installed by default, you can learn how to install it through [this guide](./admin/quickstart.mdx). + You can either pick the default Medusa backend starter, the Contentful starter or enter a starter URL by choosing `Other`: ```bash noReport @@ -89,17 +87,14 @@ You can either pick the default Medusa backend starter, the Contentful starter o The backend will be installed under the `backend` directory under the project directory. An SQLite database will be created inside that directory as well with demo data seeded into it. -### Choose Storefront Starter +### Step 3: Choose Storefront Starter After choosing the Medusa starter, you’ll be asked to choose the storefront starter. You can choose one of the starters in the list included or choose `None` to skip installing a storefront: ```bash noReport ? Which storefront starter would you like to install? -❯ Gatsby Starter -Next.js Starter +❯ Next.js Starter medusa.express (Next.js) -medusa.express (Gatsby) -Gatsby Starter (Simple) None ``` @@ -111,7 +106,7 @@ Learn more about the [Next.js starter storefront](./starters/nextjs-medusa-start ::: -### Dependency Installation +### Step 4: Wait for Dependency Installation After choosing the above starters, the installation of each component will begin along with its dependencies. Once the installation is done, you’ll see instructions related to how to start each component. @@ -122,18 +117,19 @@ Medusa API cd my-medusa-store/backend yarn start -Admin -cd my-medusa-store/admin -yarn start - Storefront cd my-medusa-store/storefront -yarn develop # for Gatsby storefront -yarn dev # for Next.js storefront +yarn dev ``` The commands will differ based on your choices in previous prompts. +:::note + +Please note that the `yarn dev` command is shown by default for storefronts and is the correct command for Medusa's Next.js storefront. If you used a different storefront, you might need to check what the correct command of that storefront is. + +::: + - Deploy to Netlify - - -## Prerequisites - -### Medusa Components - -Before proceeding with this documentation, it is assumed you already have a Medusa Admin installed locally. If not, please go through the [quickstart guide](../../admin/quickstart.mdx) first. - -Additionally, this documentation does not cover how to deploy the Medusa backend. If you want to deploy the Medusa backend, check out one of the [deployment documentation related to the Medusa backend](../server/index.mdx). - -### Needed Accounts - -- A [Netlify](https://app.netlify.com/signup) account to deploy the Medusa Admin. -- A [GitHub](https://github.com/signup) account where you will host the repository for the Medusa admin. - -:::tip - -If you want to use another Git Provider, it’s possible to follow along with this guide but you’ll have to perform the equivalent steps in your Git Provider. - -::: - -### Required Tools - -- Git’s CLI tool. You can follow [this documentation to learn how to install it for your operating system](../../development/backend/prepare-environment.mdx#git). - ---- - -## Create GitHub Repository - -Before you can deploy your Medusa Admin you need to create a GitHub repository and push the code base to it. - -On GitHub, click the plus icon at the top right, then click New Repository. - -![Create Repository](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001782/Medusa%20Docs/Netlify/0YlxBRi_aiywpo.png) - -You’ll then be redirected to a new page with a form. In the form, enter the Repository Name then scroll down and click Create repository. - -![Repository Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001800/Medusa%20Docs/Netlify/YPYXAF2_lypjne.png) - -### Push Code to GitHub Repository - -The next step is to push the code to the GitHub repository you just created. - -After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory. - -![GitHub Repository URL](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001818/Medusa%20Docs/Netlify/pHfSTuT_w544lr.png) - -Copy the link. Then, open your terminal in the directory that holds your Medusa Admin codebase and run the following commands: - -```bash -git init -git remote add origin -``` - -Where `` is the URL you just copied. - -Then, add, commit, and push the changes into the repository: - -```bash -git add . -git commit -m "initial commit" -git push origin master -``` - -After pushing the changes, you can find the files in your GitHub repository. - ---- - -## Deploy to Netlify - -This section covers how to deploy Netlify either through the Netlify website or using Netlify’s CLI tool. - -### Option 1: Using Netlify’s Website - -After logging in with Netlify, go to the [dashboard](https://app.netlify.com/). Then, at the top right of the “Sites” section, click on “Add new site”, then click on “Import an existing project” from the dropdown. - -:::note - -Alternatively, if you don’t have any other websites, you’ll see a big button that says “Import an existing project”. - -::: - -![Create a new website](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001840/Medusa%20Docs/Netlify/IUUOzoW_mw9u5w.png) - -You’ll then be asked to connect to a Git provider. - -![Connect Git Provider](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001855/Medusa%20Docs/Netlify/T6lZPDi_rvcuyf.png) - -Choose GitHub. You’ll then be redirected to GitHub’s website to give Netlify permissions if you haven’t done that before. - -After you authorize Netlify to use GitHub, you’ll be asked to pick the repository you want to deploy. Pick the repository you just created. - -![Choose Repository](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001871/Medusa%20Docs/Netlify/D0r6Q1e_th5uei.png) - -In the "Basic build settings" section, make sure the fields have the following values: - -- Base directory: (leave empty) -- Build command: yarn build -- Publish directory: public - - -Next, click the “Show advanced” button, which is above the “Deploy site” button. - -![Show advanced Button](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001884/Medusa%20Docs/Netlify/nUdwRbq_d2kmo6.png) - -Under the “Advanced build settings” section click on the “New variable” button. This will show two inputs for the key and value of the environment variable. - -For the first field enter the key `MEDUSA_BACKEND_URL` and for the value enter the URL of your Medusa backend. - -:::caution - -If you haven’t deployed your Medusa backend yet, you can leave the value blank for now and add it later. However, you will not be able to log in to the Medusa Admin without deploying the Medusa backend. - -::: - -:::note - -In previous versions of the admin, the environment variable name was `GATSBY_MEDUSA_BACKEND_URL` or `GATSBY_STORE_URL` instead. The admin remains backwards compatible, so if you've used this an older version you can keep the same environment variables. However, it's highly recommended you change it to `MEDUSA_BACKEND_URL`. - -::: - -Once you’re done, scroll down and click on Deploy site. - -You’ll be then redirected to the dashboard of the new website. Netlify will build your website in the background. You should see “Site deploy in progress” on the top card. - -![Site Deployment Progress](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001914/Medusa%20Docs/Netlify/BCnLPw7_uo9odf.png) - -The deployment can take a few minutes. - -Once the deployment is done, you’ll find the URL in the place of the “Site deploy in progress” message you saw earlier. - -![Deployment Complete](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001929/Medusa%20Docs/Netlify/fNBxCG2_jlq0q9.png) - -If you click on it, you’ll be redirected to the deployed admin website. - -![Medusa Admin Login](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001604/Medusa%20Docs/Screenshots/XYqMCo9_hq1fsv.png) - -:::note - -Before you can use Medusa Admin, you must add the URL as an environment variable on your deployed Medusa backend. Follow along in the [Configure Cross-Origin Resource Sharing (CORS) on the Medusa Backend](#configure-cors-variable-on-the-medusa-backend) section. - -::: - -### Option 2: Using Netlify’s CLI Tool - -In this section, you’ll deploy the Medusa Admin using Netlify’s CLI tool. - -#### Install the Netlify CLI tool - -If you don’t have the tool installed, run the following command to install it: - -```bash -npm install netlify-cli -g -``` - -#### Login to Netlify - -Then, run the following command to log in to Netlify in your terminal: - -```bash -netlify login -``` - -This opens a page to log in on your browser. You’ll be asked to authorize the Netlify CLI tool. - -![Authorize Application](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001985/Medusa%20Docs/Netlify/JDUdqSE_dzveww.png) - -Click on Authorize. Then, you can go back to your terminal and see that you’ve successfully logged in. - -![Authorized Message](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001998/Medusa%20Docs/Netlify/L13Yqhp_e2ejpx.png) - -#### Initialize Netlify Website - -In your terminal, run the following command: - -```bash -netlify init -``` - -You’ll have to follow five steps for the initialization: - -##### **Step 1: Create Netlify Website** - -You’ll be asked to either connect to an existing Netlify website or create a new one. Choose the second option to create a new site: - -```bash noReport -? What would you like to do? - ⇄ Connect this directory to an existing Netlify site -❯ + Create & configure a new site -``` - -##### Step 2: Choose Netlify Team - -Choose the team you want to create the website in if you have multiple teams. - -##### Step 3: Enter Site Name - -You’ll be asked to optionally enter a site name. - -##### Step 4: Configure Webhooks and Deployment Keys - -At this point, the website is created on Netlify. However, Netlify needs to configure Webhooks and deployment keys. You’ll be asked to either authorize GitHub through Netlify’s website or through a personal access token. You’re free to choose either: - -```bash noReport -? Netlify CLI needs access to your GitHub account to configure Webhooks and Depl -oy Keys. What would you like to do? (Use arrow keys) -❯ Authorize with GitHub through app.netlify.com - Authorize with a GitHub personal access token -``` - -If you pick the first option, a page in your browser will open where you have to grant authorization to your Git provider. - -If you pick the second option, you’ll need to create a personal access token on GitHub. You can follow [this guide in GitHub’s documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to learn how to do it. - -##### Last Step: Steps with Default Values - -For the rest of the steps, you can keep most of the default values provided by Netlify, except for the following options: - -1. Set build command to `yarn run build` - -``` -? Your build command (hugo build/yarn run build/etc): yarn run build -``` - -2. Set deploy directory to `public` - -``` -? Directory to deploy (blank for current dir): public -``` - -#### Set Environment Variables - -After the previous command has finished running, your Netlify website will be created. The next step is to add an environment variable that points to your Medusa backend. - -:::caution - -If you haven’t deployed your Medusa backend yet, you can leave the value blank for now and add it later. However, you will not be able to log in to the dashboard or use it without deploying the Medusa backend. - -::: - -Run the following command to add the environment variable: - -```bash -netlify env:set MEDUSA_BACKEND_URL "" -``` - -Where `` is the URL of your Medusa backend. - -:::note - -In previous versions of the admin, the environment variable name was `GATSBY_MEDUSA_BACKEND_URL` or `GATSBY_STORE_URL` instead. The admin remains backwards compatible, so if you've used this an older version you can keep the same environment variables. However, it's highly recommended you change it to `MEDUSA_BACKEND_URL`. - -::: - -#### Check deployment status - -You can check the deployment status of your website by running the following command: - -```bash -netlify watch -``` - -After the deployment has been completed, you should see a message saying “Deploy complete” with URLs to your website. - -#### Open Medusa Admin Website - -To open the Medusa Admin website, either use the URL shown to you or run the following command: - -```bash -netlify open:site -``` - -The Medusa Admin will then open in your browser. - -![Medusa Admin Login](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001604/Medusa%20Docs/Screenshots/XYqMCo9_hq1fsv.png) - -Before you can use Medusa Admin, you must add the URL as an environment variable on your deployed Medusa backend. - ---- - -## Configure CORS Variable on the Medusa Backend - -To send requests to the Medusa backend from the Medusa Admin, you must set the `ADMIN_CORS` environment variable on your backend to the Medusa Admin’s URL. - -:::caution - -If you want to set a custom domain to your Medusa Admin website on Netlify, make sure to do it before this step. You can refer to this guide on [Netlify’s documentation to learn how to add a custom domain](https://docs.netlify.com/domains-https/custom-domains/#assign-a-domain-to-a-site). - -::: - -On your Medusa backend, add the following environment variable: - -```bash -ADMIN_CORS= -``` - -Where `` is the URL of your Medusa Admin that you just deployed. - -Then, restart your Medusa backend. Once the backend is running again, you can log in to the Medusa Admin and use it. - ---- - -## See Also - -- [Deploy storefront](../storefront/index.mdx) -- [Configure Medusa backend](../../development/backend/configurations.md) diff --git a/docs/content/deployments/admin/deploying-on-vercel.md b/docs/content/deployments/admin/deploying-on-vercel.md new file mode 100644 index 0000000000..1009456c91 --- /dev/null +++ b/docs/content/deployments/admin/deploying-on-vercel.md @@ -0,0 +1,197 @@ +--- +description: 'Learn step-by-step.' +addHowToData: true +--- + +# Deploy Admin to Vercel + +In this document, you’ll learn how to deploy the admin dashboard to Vercel. + +## Prerequisites + +### Medusa Components + +You must have a [Medusa backend](../../development/backend/install.mdx) installed along with an [admin dashboard plugin](../../admin/quickstart.mdx). + +### Required Accounts + +- [Vercel Account](https://vercel.com/) +- [GitHub Account](https://github.com/): Only required if you’re deploying through the Vercel website. + +:::note + +If you want to use another Git Provider, it’s possible to follow along with this guide, but you’ll have to perform the equivalent steps in your Git Provider. + +::: + +### Required Tools + +- [Git CLI](../../development/backend/prepare-environment.mdx#git): Only required if you’re deploying through the Vercel website. + +--- + +## Step 1: Create GitHub Repository + +:::note + +This step is only required if you’re deploying from the Vercel website. However, it’s highly recommended to connect your Vercel project to a Git repository for a better developer experience. + +::: + +Before you can deploy your admin dashboard, you need to create a GitHub repository and push the code base to it. To do that: + +1. On GitHub, click the plus icon at the top right, then click New Repository. +2. You’ll then be redirected to a new page with a form. In the form, enter the Repository Name. +3. Scroll down and click Create repository. + +### Push Code to GitHub Repository + +The next step is to push the code to the GitHub repository you just created. + +After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory. + +Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands: + +```bash +git init +git remote add origin +``` + +Where `` is the URL you just copied. + +Then, add, commit, and push the changes into the repository: + +```bash +git add . +git commit -m "initial commit" +git push +``` + +After pushing the changes, you can find the files in your GitHub repository. + +--- + +## Step 2: Configure Build Script + +In the `package.json` of the Medusa backend, add or change a build script for the admin: + +```json +"scripts": { + // other scripts + "build:admin": "medusa-admin build --deployment", +} +``` + +### Additional Build Options + +Aside from `--deployment`, you can use the following options when building your admin for deployment: + +- `--backend` or `-b`: a string specifying the URL of the Medusa backend. The default here is the value of the environment variable `MEDUSA_BACKEND_URL`. If this options is added, the value you set for `MEDUSA_BACKEND_URL` in Vercel will no longer have an effect. For example, `medusa-admin build --deployment --backend example.com` +- `--out-dir` or `-o`: a string specifying a custom path to output the build files to. By default, it will be the `build` directory. For example, `medusa-admin --deployment --out-dir public`. +- `--include` or `-i`: a list of strings of paths to files you want to include in the build output. It can be useful if you want to inject files that are relevant to your hosting. For example, `medusa-admin --deployment --include 200.html` +- `--include-dist` or `-d`: a string specifying the path to copy the files specified in `--include` to. By default, the files are coopied to the root of the build directory. You can use this option to change that. For example, `medusa-admin --deployment --include 200.html --include-dist static`. + +--- + +## Step 3: Add Vercel Configurations + +In the root directory of the Medusa backend, create a new file `vercel.json` with the following content: + +```json +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} +``` + +--- + +## Step 4: Push Changes to GitHub + +After making all the previous changes, push them to GitHub before starting the deployment on Vercel: + +```bash +git add . +git commit -m "prepare repository for deployment" +git push +``` + +--- + +## Step 5: Deploy to Vercel + +This section covers how to deploy the admin, either using the Vercel website or using Vercel’s CLI tool. + +### Option 1: Using the Vercel Website + +This section explains how to deploy the admin using the Vercel website: + +1. Open the [Vercel dashboard](https://vercel.com/dashboard) after logging in. +2. Click on the “Add New…” button next to the search bar. +3. Choose Project from the dropdown. +4. In the new page that opens, find the Git repository that holds your medusa backend and click on the Import button. If you haven’t connected your Vercel account to any Git provider, you must do that first. +5. In the Configure Project form: + 1. Set the Framework Preset to Vite. + 2. Open the Build and Output Settings collapsible, and set the Build Command to `yarn build:admin` and the Output Directory to `build`. If you’ve configured the admin to use a different output directory, then change it to that directory. + 3. Open the Environment Variables collapsible, and add an environment variable with the name `MEDUSA_BACKEND_URL` with the value being the URL to your deployed Medusa backend. + 4. You can optionally edit the Project Name. +6. Once you’re done, click on the “Deploy” button. + +This will start the deployment of the admin. Once it’s done, you’ll be redirected to the main dashboard of your new project. + +:::note + +At this point, when you visit the admin, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the admin. Before you start using the admin, follow along the [Configure CORS on the Medusa Backend](#step-6-configure-cors-on-the-medusa-backend) section. + +::: + +### Option 2: Using Vercel’s CLI Tool + +This section explains how to deploy the admin using the Vercel CLI tool. You should have the CLI tool installed first, as explained in [Vercel’s documentation](https://vercel.com/docs/cli). + +In the directory of your Medusa backend, run the following command to deploy your admin: + +```bash +vercel --build-env MEDUSA_BACKEND_URL= +``` + +Where `` is the URL of your deployed Medusa backend. + +You’ll then be asked to log in if you haven’t already, and to choose the scope to deploy your project to. You can also decide to link the admin to an existing project, or change the project’s name. + +When asked, ”In which directory is your code located?”, keep the default `./` and just press Enter. + +The project setup will then start. When asked if you want to modify the settings, answer `y`. You’ll then be asked a series of questions: + +1. “Which settings would you like to overwrite”: select Build Command and Output Directory using the space bar, then press Enter. +2. “What's your **Build Command**?”: enter `yarn build:admin`. +3. “What's your **Output Directory**?”: enter `build`. + +After that, it will take a couple of minutes for the deployment to finish. The link to the admin will be shown in the final output of the command. + +:::note + +At this point, when you visit the admin, you will face errors related to Cross-Origin Resource Sharing (CORS) while using the admin. Before you start using the admin, follow along the [Configure CORS on the Medusa Backend](#step-6-configure-cors-on-the-medusa-backend) section. + +::: + +--- + +## Step 6: Configure CORS on the Medusa Backend + +To send requests to the Medusa backend from the admin dashboard, you must set the `ADMIN_CORS` environment variable on your backend to the admin’s URL. + +:::note + +If you want to set a custom domain to your admin dashboard on Vercel, make sure to do it before this step. You can refer to this guide on [Vercel’s documentation](https://vercel.com/docs/concepts/projects/domains/add-a-domain). + +::: + +On your Medusa backend, add the following environment variable: + +```bash +ADMIN_CORS= +``` + +Where `` is the URL of your admin dashboard that you just deployed. + +Then, restart your Medusa backend. Once the backend is running again, you can use your admin dashboard. diff --git a/docs/content/deployments/server/deploying-on-digital-ocean.md b/docs/content/deployments/server/deploying-on-digital-ocean.md index d891cde8aa..aadee80f61 100644 --- a/docs/content/deployments/server/deploying-on-digital-ocean.md +++ b/docs/content/deployments/server/deploying-on-digital-ocean.md @@ -83,20 +83,14 @@ Before you can deploy your Medusa backend you need to create a GitHub repository On GitHub, click the plus icon at the top right, then click New Repository. -![Click plus then choose new repository from dropdown](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001782/Medusa%20Docs/Netlify/0YlxBRi_aiywpo.png) - You’ll then be redirected to a new page with a form. In the form, enter the Repository Name then scroll down and click Create repository. -![New repository form](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001800/Medusa%20Docs/Netlify/YPYXAF2_lypjne.png) - ### Push Code to GitHub Repository The next step is to push the code to the GitHub repository you just created. After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory. -![GitHub repository's URL](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001818/Medusa%20Docs/Netlify/pHfSTuT_w544lr.png) - Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands: ```bash @@ -111,7 +105,7 @@ Then, add, commit, and push the changes into the repository: ```bash git add . git commit -m "initial commit" -git push origin master +git push ``` After pushing the changes, you can find the files in your GitHub repository. @@ -197,13 +191,19 @@ NPM_CONFIG_PRODUCTION=false NODE_ENV=production ``` +:::caution + +It’s highly recommended to use strong, randomly generated secrets for `JWT_SECRET` and `COOKIE_SECRET`. + +::: + Notice how for database environment variables you access the values from the database you created earlier `db`. If you changed the name of the database, you must change `db` here to the name you supplied to the PostgreSQL database. Another thing to note here is that you added a `REDIS_URL` environment variable that uses a `redis` resource to retrieve the URL. You’ll be creating this resource in a later section. -:::caution +:::note -It’s highly recommended to use strong, randomly generated secrets for `JWT_SECRET` and `COOKIE_SECRET`. +If you're using modules that require setting environment variables, make sure to set them here. You can also add them later. For example, if you're using the Redis Event Bus module, you can set the environment variable for it here or use the same `REDIS_URL` variable. Your module may also require setting up other resources than those explained in this guide so make sure to add them as well. ::: @@ -300,5 +300,5 @@ Once you click Save, the environment variables will be saved and a redeployment ## See Also -- [Deploy the Medusa Admin to Netlify](../admin/deploying-on-netlify.md). -- [Deploy the Gatsby Storefront to Netlify](../storefront/deploying-gatsby-on-netlify.md). +- [Deploy the Medusa Admin](../admin/index.mdx). +- [Deploy the Storefront](../storefront/index.mdx). diff --git a/docs/content/deployments/server/deploying-on-heroku.mdx b/docs/content/deployments/server/deploying-on-heroku.mdx index 79428cdcc0..bead355338 100644 --- a/docs/content/deployments/server/deploying-on-heroku.mdx +++ b/docs/content/deployments/server/deploying-on-heroku.mdx @@ -17,12 +17,6 @@ Alternatively, you can use this button to deploy the Medusa backend to Heroku di Deploy to Heroku -
- -
- ## Prerequisites ### Medusa Backend @@ -148,6 +142,20 @@ If you're using the Heroku PostgreSQL Add-on, it should configure the environmen However, if you use another add-on, make sure to set the environment variable `DATABASE_URL` to the PostgreSQL Database URL. +#### (Optional) Configure Modules Environment Variables + +If you use in your Medusa backend modules that require setting environment variables, then you should set them at this point. + +For example, if you use the Redis Event Bus module: + +```bash +heroku config:set EVENT_REDIS_URL= +``` + +Make sure to change `EVENT_REDIS_URL` to the environment variable name you use for your module's configurations. + +If your module requires setting up other services, do that then add the environment variables. + #### (Optional) Configure CORS Variables Optionally, if you've deployed the admin dashboard and you want to ensure it can use the backend's REST APIs, you must set the following environment variable: diff --git a/docs/content/deployments/server/deploying-on-qovery.md b/docs/content/deployments/server/deploying-on-qovery.md index 3e63f4320e..47b886360f 100644 --- a/docs/content/deployments/server/deploying-on-qovery.md +++ b/docs/content/deployments/server/deploying-on-qovery.md @@ -51,20 +51,14 @@ Before you can deploy your Medusa backend you need to create a GitHub repository On GitHub, click the plus icon at the top right, then click New Repository. -![Click plus icon at the top right](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001782/Medusa%20Docs/Netlify/0YlxBRi_aiywpo.png) - You’ll then be redirected to a new page with a form. In the form, enter the Repository Name then scroll down and click Create repository. -![An image of the Create Repository form](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001800/Medusa%20Docs/Netlify/YPYXAF2_lypjne.png) - ### Push Code to GitHub Repository The next step is to push the code to the GitHub repository you just created. After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory. -![An image of the GitHub URL in a new repository](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001818/Medusa%20Docs/Netlify/pHfSTuT_w544lr.png) - Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands: ```bash @@ -79,7 +73,7 @@ Then, add, commit, and push the changes into the repository: ```bash git add . git commit -m "initial commit" -git push origin master +git push ``` After pushing the changes, you can find the files in your GitHub repository. @@ -118,7 +112,7 @@ You need to add some variables to use for your Medusa deployment to Qovery. In the root directory of your Medusa backend, create the file `variables.tf` with the following content: -``` +```tf variable "qovery_organization_id" { type = string nullable = false @@ -255,6 +249,12 @@ Here’s an explanation of each of the variables and how to retrieve their varia - `git_branch`: The branch to use in the GitHub repo. By default it’s `master`. - `git_root_path`: The root path of the Medusa backend. By default, it’s `/`. If you are hosting your Medusa backend in a monorepo in a nested directory, you need to change the value of this variable. +:::note + +If you're using modules that require setting up other variables, you can add them here. + +::: + ### Add Terraform Configuration File In the root directory of your Medusa backend, create the file `main.tf` with the following content: @@ -422,6 +422,12 @@ resource "qovery_application" "medusa_app" { } ``` +:::note + +If you're using modules that require setting up other resources, make sure to add them here. + +::: + This is a Terraform configuration file that creates all the resources necessary to deploy Medusa to Qovery. If you set `qovery_create_cluster` to `true`, it will create new credentials and a cluster in your Qovery organization using the AWS credentials you set in the variables. Next, it creates a new project, environment, PostgreSQL database, and a Redis database in your Qovery organization. @@ -521,5 +527,5 @@ To add environment variables, in your [Qovery Console](https://console.qovery.co ## See Also -- [Deploy the Medusa Admin to Netlify](../admin/deploying-on-netlify.md) -- [Deploy the Gatsby Storefront to Netlify](../storefront/deploying-gatsby-on-netlify.md) +- [Deploy the Medusa Admin](../admin/index.mdx) +- [Deploy the Storefront](../storefront/index.mdx) diff --git a/docs/content/deployments/server/deploying-on-railway.md b/docs/content/deployments/server/deploying-on-railway.md index 136c061e8f..e6253f6e2a 100644 --- a/docs/content/deployments/server/deploying-on-railway.md +++ b/docs/content/deployments/server/deploying-on-railway.md @@ -48,20 +48,14 @@ Before you can deploy your Medusa backend you need to create a GitHub repository On GitHub, click the plus icon at the top right, then click New Repository. -![Click plus then choose new repository from dropdown](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001782/Medusa%20Docs/Netlify/0YlxBRi_aiywpo.png) - You’ll then be redirected to a new page with a form. In the form, enter the Repository Name then scroll down and click Create repository. -![New repository form](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001800/Medusa%20Docs/Netlify/YPYXAF2_lypjne.png) - ### Push Code to GitHub Repository The next step is to push the code to the GitHub repository you just created. After creating the repository, you’ll be redirected to the repository’s page. On that page, you should see a URL that you can copy to connect your repository to a local directory. -![GitHub repository's URL](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001818/Medusa%20Docs/Netlify/pHfSTuT_w544lr.png) - Copy the link. Then, open your terminal in the directory that holds your Medusa backend codebase and run the following commands: ```bash @@ -76,7 +70,7 @@ Then, add, commit, and push the changes into the repository: ```bash git add . git commit -m "initial commit" -git push origin master +git push ``` After pushing the changes, you can find the files in your GitHub repository. @@ -118,6 +112,11 @@ To find the Redis database URL which you’ll need later: 2. Choose the Connect tab. 3. Copy the Redis Connection URL. + +### Note about Modules + +If you use modules that require setting up other resources, make sure to add them at this point. This guide does not cover configurations specific to a module. + ### Deploy the Medusa Backend Repository In the same project view: @@ -217,5 +216,5 @@ For example, you can run commands on the backend to seed the database or create ## See Also -- [Deploy the Medusa Admin to Netlify](../admin/deploying-on-netlify.md) +- [Deploy the Medusa Admin](../admin/index.mdx) - [Deploy the Gatsby Storefront to Netlify](../storefront/deploying-gatsby-on-netlify.md) diff --git a/docs/content/development/backend/configurations.md b/docs/content/development/backend/configurations.md index 9e2e56e31f..627a3bc647 100644 --- a/docs/content/development/backend/configurations.md +++ b/docs/content/development/backend/configurations.md @@ -1,5 +1,5 @@ --- -description: 'Learn about the different configurations available in a Medusa backend. This includes configurations related to the database, CORS, plugins, redis, and more.' +description: 'Learn about the different configurations available in a Medusa backend. This includes configurations related to the database, CORS, plugins, and more.' --- # Configure Medusa Backend @@ -8,32 +8,18 @@ In this document, you’ll learn what configurations you can add to your Medusa ## Prerequisites -This document assumes you already followed along with the [“Set up your development environment” documentation](./prepare-environment.mdx) and have [installed a Medusa backend](./install.mdx#create-a-medusa-backend). +This document assumes you already followed along with the [Prepare Environment documentation](./prepare-environment.mdx) and have [installed a Medusa backend](./install.mdx#create-a-medusa-backend). --- ## Medusa Configurations File -The configurations for your Medusa backend are in `medusa-config.js`. This includes database, Redis, and plugin configurations, among other configurations. +The configurations for your Medusa backend are in `medusa-config.js`. This includes database, modules, and plugin configurations, among other configurations. Some of the configurations mentioned in this document are already defined in `medusa-config.js` with default values. It’s important that you know what these configurations are used for and how to set them. --- -## Environment Variables - -In your configurations, you’ll often use environment variables. For example, when using API keys or setting your database URL. - -By default, Medusa loads environment variables from the system’s environment variables. Any different method you prefer to use or other location you’d prefer to load environment variables from you need to manually implement. - -:::info - -This change in how environment variables are loaded was introduced in version 1.3.0. You can learn more in the [upgrade guide for version 1.3.0](../../upgrade-guides/medusa-core/1-3-0.md). - -::: - ---- - ## Database Configuration Medusa supports two database types: SQLite and PostgreSQL. @@ -131,9 +117,15 @@ module.exports = { ## Redis -Medusa uses Redis to handle the event queue, among other usages. You need to set Redis URL in the configurations: +:::note -```jsx +As of v1.8 of the Medusa core package, Redis is only used for scheduled jobs. For events handling, it's now moved into a module and can be configured as explained [here](#modules). + +::: + +You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/). You need to set Redis URL in the configurations: + +```js module.exports = { projectConfig: { // ...other configurations @@ -144,7 +136,7 @@ module.exports = { Where `REDIS_URL` is the URL used to connect to Redis. The format of the connection string is `redis[s]://[[username][:password]@][host][:port][/db-number]`. -If you omit this configuration, events will not be emitted and subscribers will not work. +If you omit this configuration, scheduled jobs will not work. :::tip @@ -160,12 +152,6 @@ REDIS_URL= Where `` is the URL of your Redis backend. -:::info - -You can learn more about Subscribers and events in the [Subscriber documentation](../events/create-subscriber.md). - -::: - --- ## JWT Secret @@ -203,7 +189,7 @@ In a development environment, if this option is not set the default secret is This configuration is used to sign the session ID cookie. To set the cookie secret: -```jsx +```js module.exports = { projectConfig: { // ...other configurations @@ -273,7 +259,7 @@ The examples above apply to both Admin and Store CORS. To make sure your Admin dashboard can access the Medusa backend’s admin endpoints, set this configuration: -```jsx +```js module.exports = { projectConfig: { // ...other configurations @@ -302,7 +288,7 @@ Make sure that the URL is without a backslash at the end. For example, you shoul To make sure your Storefront dashboard can access the Medusa backend, set this configuration: -```jsx +```js module.exports = { projectConfig: { // ...other configurations @@ -331,7 +317,7 @@ Make sure that the URL is without a backslash at the end. For example, you shoul ## Plugins -On your Medusa backend, you can use Plugins to add custom features or integrate third-party services. For example, installing a plugin to use Stripe as a payment provider. +On your Medusa backend, you can use Plugins to add custom features or integrate third-party services. For example, installing a plugin to use Stripe as a payment processor. :::info @@ -385,7 +371,61 @@ It is recommended to use environment variables to store values of options instea --- +## Modules + +In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain modules with custom implementations. + +:::tip + +You can learn more about Modules in the [Modules Overview documentation](../modules/overview.mdx). + +::: + +Aside from installing the module with NPM, you need to add into the exported object in `medusa-config.js`. For example: + +```js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "", + options: { + // options if necessary + }, + }, + }, +} +``` + +`moduleType` and `` are just placeholder that should be replaced based on the type of Module you're adding. For example, if you used the default Medusa starter to create your backend, you should have an `eventBus` module installed: + +```js +module.exports = { + // ... + modules: { + // ... + eventBus: { + resolve: "@medusajs/event-bus-local", + }, + }, +} +``` + +Each module can have its own options. You must refer to the module's documentation to learn about its options. + +### Recommended Event Bus Modules + +In the default Medusa starter, the local event bus module is used. This module is good for testing out Medusa and during development, but it's highly recommended to use the [Redis event module](../events/modules/redis.md) instead for better development experience and during production. + +### Recommended Cache Modules + +In the default Medusa starter, the in-memory cache module is used. This module is good for testing out Medusa and during development, but it's highly recommended to use the [Redis cache module](../cache/modules/redis.md) instead for better development experience and during production. + +--- + ## See Also - [Medusa architecture overview](../fundamentals/architecture-overview.md) -- [Plugins](../plugins/overview.mdx) \ No newline at end of file +- [Plugins](../plugins/overview.mdx) +- [Modules](../modules/overview.mdx) diff --git a/docs/content/development/backend/install.mdx b/docs/content/development/backend/install.mdx index bc87ac1eea..8d393d9e6e 100644 --- a/docs/content/development/backend/install.mdx +++ b/docs/content/development/backend/install.mdx @@ -85,7 +85,7 @@ Medusa backend is made up of different resources that make it a powerful server. ### Set Up Development Environment -For an optimal experience developing with Medusa and to make sure you can use its advanced functionalities, you'll need to install more tools such as Redis or PostgreSQL. +For an optimal experience developing with Medusa and to make sure you can use its advanced functionalities, you'll need to install more tools such as PostgreSQL. Follow [this documentation to learn how to set up your development environment](../../development/backend/prepare-environment.mdx). diff --git a/docs/content/development/backend/prepare-environment.mdx b/docs/content/development/backend/prepare-environment.mdx index f8e5d213d6..005b311fa1 100644 --- a/docs/content/development/backend/prepare-environment.mdx +++ b/docs/content/development/backend/prepare-environment.mdx @@ -1,5 +1,5 @@ --- -description: 'Learn how to prepare your development environment while using Medusa. This guide includes how to install Node.js, Git, Medusa CLI tool, PostgreSQL, and Redis.' +description: 'Learn how to prepare your development environment while using Medusa. This guide includes how to install Node.js, Git, Medusa CLI tool, and PostgreSQL.' --- import styles from './development.module.css'; @@ -213,73 +213,6 @@ Where: -### Redis - -Medusa uses Redis as the event queue in the backend. If you want to use subscribers to handle events such as when an order is placed and perform actions based on the events, then you need to install and configure Redis. - -If you don’t install and configure Redis with your Medusa backend, then it will work without any events-related features. - -:::tip - -After installing Redis, check out the [Configure your Backend documentation](./configurations.md#redis) to learn how to configure Redis to work with Medusa. - -::: - - - - -To use Redis on Windows, you must have [Windows Subsystem for Linux (WSL2) enabled](https://docs.microsoft.com/en-us/windows/wsl/install). This lets you run Linux binaries on Windows. - -After installing and enabling WSL2, if you use an Ubuntu distribution you can run the following commands to install Redis: - -```bash -sudo apt-add-repository ppa:redislabs/redis -sudo apt-get update -sudo apt-get upgrade -sudo apt-get install redis-server - -## Start Redis server -sudo service redis-server start -``` - - - - -If you use Ubuntu you can use the following commands to install Redis: - -```bash -curl -fsSL https://packages.redis.io/gpg | \ - sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg - -echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" \ - | sudo tee /etc/apt/sources.list.d/redis.list - -sudo apt-get update -sudo apt-get install redis - -## Start Redis server -sudo service redis-server start -``` - -For other distributions, you can check out [Redis’ guide on this](https://redis.io/docs/getting-started/installation/install-redis-on-linux/). - - - - -You can install Redis on macOS using Homebrew with the following command: - -```bash -brew install redis - -## Start Redis server -brew services start redis -``` - -To install Redis without Homebrew you can check out [Redis’s guide on installing it from source](https://redis.io/docs/getting-started/installation/install-redis-from-source/). - - - - --- ## See Also diff --git a/docs/content/development/batch-jobs/create.mdx b/docs/content/development/batch-jobs/create.mdx index 0b80d7c967..eb222f7a48 100644 --- a/docs/content/development/batch-jobs/create.mdx +++ b/docs/content/development/batch-jobs/create.mdx @@ -28,11 +28,7 @@ This documentation helps you learn how to create a batch job strategy. The batch ### Medusa Components -It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../backend/prepare-environment.mdx#redis) and [configure it with your Medusa backend](../backend/configurations.md#redis). +It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### PostgreSQL diff --git a/docs/content/development/batch-jobs/customize-import.md b/docs/content/development/batch-jobs/customize-import.md index 5b1018b387..dae21b3e4e 100644 --- a/docs/content/development/batch-jobs/customize-import.md +++ b/docs/content/development/batch-jobs/customize-import.md @@ -19,11 +19,7 @@ Although this documentation specifically targets import strategies, you can use ### Medusa Components -It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../backend/prepare-environment.mdx#redis) and [configure it with your Medusa backend](../backend/configurations.md#redis). +It's assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### PostgreSQL diff --git a/docs/content/development/batch-jobs/index.mdx b/docs/content/development/batch-jobs/index.mdx index d501386820..1efeac73aa 100644 --- a/docs/content/development/batch-jobs/index.mdx +++ b/docs/content/development/batch-jobs/index.mdx @@ -13,7 +13,7 @@ In this document, you’ll learn what Batch Jobs are and how they work in Medusa Batch Jobs are tasks that can be performed asynchronously and iteratively. They can be [created using the Admin API](/api/admin/#tag/Batch-Job/operation/PostBatchJobs), then, once confirmed, they are processed asynchronously. -Batch jobs require Redis, which Medusa uses as a queuing system to register and handle events. Every status change of a batch job triggers an event that can be handled using [subscribers](../events/subscribers.mdx). +Every status change of a batch job triggers an event that can be handled using [subscribers](../events/subscribers.mdx). Medusa uses batch jobs in its core to perform some asynchronous tasks. For example, the Export Products functionality uses batch jobs. diff --git a/docs/content/development/cache/create.md b/docs/content/development/cache/create.md new file mode 100644 index 0000000000..b092a8471e --- /dev/null +++ b/docs/content/development/cache/create.md @@ -0,0 +1,265 @@ +--- +description: 'In this document, you’ll learn about how to create a cache module in Medusa, using Memcached as an example.' +--- + +# How to Create a Cache Module + +In this document, you will learn how to build your own Medusa cache module. + +## Overview + +Medusa provides ready-made modules for cache, including in-memory and Redis modules. If you prefer another technology used for caching in your commerce application, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances. + +In this document, you will learn how to build your own Medusa cache module based on Memcached as an example. This gives you a real-life example of creating the cache module. You can follow the general steps with any other caching system or service. + +--- + +## Prerequisites + +If you want to create the Memcached cache module as explained in this guide, you must have [Memcached](https://memcached.org/) installed and running. + +--- + +## (Optional) Step 0: Prepare Module Directory + +Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation. + +You can refer to the [Project Preparation step in the Create Module documentation](../modules/create.mdx#optional-step-0-project-preparation) to learn how to do that. + +--- + +## Step 1: Create the Service + +Create the file `services/memcached-cache.ts` which will hold your cache service. Note that the name of the file is recommended to be in the format `-cache`. So, if you’re not integrating `memcached`, you should replace the name with what’s relevant for your module. + +Add the following content to the file: + +```ts title=services/memcached-cache.ts +import { ICacheService } from "@medusajs/types" + +class MemcachedCacheService implements ICacheService { + async get(key: string): Promise { + throw new Error("Method not implemented.") + } + async set( + key: string, + data: unknown, + ttl?: number + ): Promise { + throw new Error("Method not implemented.") + } + async invalidate(key: string): Promise { + throw new Error("Method not implemented.") + } +} + +export default MemcachedCacheService +``` + +This creates the class `MemcachedCacheService` that implements the `ICacheService` interface imported from `@medusajs/types`. Feel free to rename the class to what’s relevant for your cache service. + +In the class, you must implement three methods: `get`, `set`, and `invalidate`. You’ll learn what each of these methods do in the upcoming section. + +--- + +## Step 2: Implement the Methods + +### constructor + +The `constructor` method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the module’s options which are typically defined in `medusa-config.js`, and to other services and resources in the Medusa backend using [dependency injection](../fundamentals/dependency-injection.md). + +Here’s an example of how you can use the `constructor` to create a memcached instance and save the module’s options: + +```ts title=src/services/memcached-cache.ts +import { ICacheService } from "@medusajs/types" +import Memcached from "memcached" + +const DEFAULT_CACHE_TIME = 30 + +export type MemcachedCacheModuleOptions = { + /** + * Time to keep data in the cache (in seconds) + */ + ttl?: number + + /** + * Allow passing the configuration for Memcached client + */ + location: Memcached.Location + options?: Memcached.options +} + +class MemcachedCacheService implements ICacheService { + protected readonly memcached: Memcached + protected readonly TTL: number + + constructor( + { + // inject services through dependency injection + // for example you can access the logger + logger, + }, + options: MemcachedCacheModuleOptions + ) { + this.memcached = new Memcached( + options.location, + options.options + ) + this.TTL = options.ttl || DEFAULT_CACHE_TIME + } + // ... +} +``` + +### get + +The `get` method allows you to retrieve the value of a cached item based on its key. The method accepts a string as a first parameter, which is the key in the cache. It either returns the cached item or `null` if it doesn’t exist. + +Here’s an example implementation of this method for a Memcached service: + +```ts title=src/services/memcached-cache.ts +class MemcachedCacheService implements ICacheService { + // ... + async get(cacheKey: string): Promise { + return new Promise((res, rej) => { + this.memcached.get(cacheKey, (err, data) => { + if (err) { + res(null) + } else { + if (data) { + res(JSON.parse(data)) + } else { + res(null) + } + } + }) + }) + } +} +``` + +### set + +The `set` method is used to set an item in the cache. It accepts three parameters: + +1. The first parameter `key` is a string that represents the key of the data being added to the cache. This key can be used later to get or invalidate the cached item. +2. The second parameter `data` is the data to be added to the cache. There’s no defined type for this data. +3. The third parameter `ttl` is optional. It’s a number indicating how long (in seconds) the data should be kept in the cache. + +Here’s an example of an implementation of this method for a Memcached service: + +```ts title=src/services/memcached-cache.ts +class MemcachedCacheService implements ICacheService { + // ... + async set( + key: string, + data: Record, + ttl: number = this.TTL + ): Promise { + return new Promise((res, rej) => + this.memcached.set( + key, JSON.stringify(data), ttl, (err) => { + if (err) { + rej(err) + } else { + res() + } + }) + ) + } +} +``` + +### invalidate + +The `invalidate` method removes an item from the cache using its key. By default, the item is removed from the cache when the time-to-live (ttl) expires. The `invalidate` method can be used to remove the item beforehand. + +The method accepts a string as a first parameter, which is the key of the item to invalidate and remove from the cache. + +Here’s an example of an implementation of this method for a Memcached service: + +```ts title=src/services/memcached-cache.ts +class MemcachedCacheService implements ICacheService { + // ... + async invalidate(key: string): Promise { + return new Promise((res, rej) => { + this.memcached.del(key, (err) => { + if (err) { + rej(err) + } else { + res() + } + }) + }) + } +} +``` + +--- + +## Step 3: Export the Service + +After implementing the cache service, you must export it so that the Medusa backend can use it. + +Create the file `index.ts` with the following content: + +```ts title=index.ts +import { ModuleExports } from "@medusajs/modules-sdk" + +import { MemcachedCacheService } from "./services" + +const service = MemcachedCacheService + +const moduleDefinition: ModuleExports = { + service, +} + +export default moduleDefinition +``` + +This exports a module definition, which requires at least a `service`. If you named your service something other than `MemcachedCacheService`, make sure to replace it with that. + +You can learn more about what other properties you can export in your module definition in the [Create a Module documentation](../modules/create.mdx#step-2-export-module). + +--- + +## Step 4: Test your Module + +You can test your module in the Medusa backend by referencing it in the configurations. + +To do that, add the module to the exported configuration in `medusa-config.js` as follows: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + cacheService: { + resolve: "path/to/custom-module", + options: { + // any necessary options + ttl: 30, + location: "localhost:55000", + }, + }, + }, +} +``` + +Make sure to replace the `path/to/custom-module` with a relative path from your Medusa backend to your module. You can learn more about module reference in the [Create Module documentation](../modules/create.mdx#module-reference). + +You can also add any necessary options to the module. The options added in the example above are relevant to the memcached module and you can replace them with your own options. + +Then, to test the module, run the Medusa backend which also runs your module: + +```bash npm2yarn +npm run start +``` + +--- + +## (Optional) Step 5: Publish your Module + +You can publish your cache module to NPM. This can be useful if you want to reuse your module across Medusa backend instances, or want to allow other developers to use it. + +You can refer to the [Publish Module documentation](../modules/publish.md) to learn how to publish your module. diff --git a/docs/content/development/cache/modules/in-memory.md b/docs/content/development/cache/modules/in-memory.md new file mode 100644 index 0000000000..9a4a175288 --- /dev/null +++ b/docs/content/development/cache/modules/in-memory.md @@ -0,0 +1,70 @@ +--- +description: 'In this document, you’ll learn about the in-memory cache module and how you can install it in your Medusa backend.' +--- + +# In-Memory Cache Module + +In this document, you’ll learn about the in-memory cache module and how you can install it in your Medusa backend. + +## Overview + +Medusa’s modular architecture allows developers to extend or completely replace the logic used for caching. You can create a custom module, or you can use the modules Medusa provides. + +Medusa’s default starter project uses the in-memory cache module. The in-memory cache module uses a plain JavaScript Map object to store the cache data. + +This module is helpful for development or when you’re testing out Medusa, but it’s not recommended to be used in production. For production, it’s recommended to use modules like [Redis Cache Module](./redis.md). + +This document will guide you through installing the in-memory cache module. + +--- + +## Prerequisites + +### Medusa Backend + +It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx). + +--- + +## Step 1: Install the Module + +In the root directory of your Medusa backend, install the in-memory cache module with the following command: + +```bash npm2yarn +npm install @medusajs/cache-inmemory +``` + +--- + +## Step 2: Add Configuration + +In `medusa-config.js`, add the following to the exported object: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + cacheService: { + resolve: "@medusajs/cache-inmemory", + options: { + ttl: 30, + }, + }, + }, +} +``` + +This registers the in-memory cache module as the main cache service to use. You pass it the option `ttl`. This means time-to-live, and it indicates the number of seconds an item can live in the cache before it’s removed. + +--- + +## Step 3: Test Module + +To test the module, run the following command to start the Medusa backend: + +```bash npm2yarn +npm run start +``` + +The backend should then start with no errors, indicating that the module was installed successfully. diff --git a/docs/content/development/cache/modules/redis.md b/docs/content/development/cache/modules/redis.md new file mode 100644 index 0000000000..f16cfc9be9 --- /dev/null +++ b/docs/content/development/cache/modules/redis.md @@ -0,0 +1,92 @@ +--- +description: 'In this document, you’ll learn about the Redis cache module and how you can install it in your Medusa backend.' +--- + +# Redis Cache Module + +In this document, you’ll learn about the Redis cache module and how you can install it in your Medusa backend. + +## Overview + +Medusa’s modular architecture allows developers to extend or replace the logic used for [caching](../overview.mdx). You can create a custom module, or you can use the modules Medusa provides. + +One of these modules is the Redis module. This module allows you to utilize Redis for the caching functionality. This document will you guide you through installing the Redis module. + +--- + +## Prerequisites + +### Medusa Backend + +It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx). + +### Redis + +You must have Redis installed and configured in your Medusa backend. You can learn how to install it from the [Redis documentation](https://redis.io/docs/getting-started/installation/). + +--- + +## Step 1: Install the Module + +In the root directory of your Medusa backend, install the Redis cache module with the following command: + +```bash npm2yarn +npm install @medusajs/cache-redis +``` + +--- + +## Step 2: Add Environment Variable + +The Redis cache module requires a connection URL to Redis as part of its options. If you don’t already have an environment variable set for a Redis URL, make sure to add one: + +```bash +CACHE_REDIS_URL= +``` + +Where `` is a connection URL to your Redis instance. + +--- + +## Step 3: Add Configuration + +In `medusa-config.js`, add the following to the exported object: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + cacheService: { + resolve: "@medusajs/cache-redis", + options: { + redisUrl: process.env.CACHE_REDIS_URL, + ttl: 30, + }, + }, + }, +} +``` + +This registers the Redis cache module as the main cache service to use. In the options, you pass `redisUrl` with the value being the environment variable you set. You also pass the option `ttl`. This means time-to-live, and it indicates the number of seconds an item can live in the cache before it’s removed. + +Other available options include: + +- `redisOptions`: an object containing options for the Redis instance. You can learn about available options in [io-redis’s documentation](https://luin.github.io/ioredis/index.html#RedisOptions). By default, it’s an empty object. +- `namespace`: a string used to prefix event keys. By default, it's `medusa`. + +--- + +## Step 4: Test Module + +To test the module, run the following command to start the Medusa backend: + +```bash npm2yarn +npm run start +``` + +If the module was installed successfully, you should see the following message in the logs: + +```bash noCopy noReport +Connection to Redis in module 'cache-redis' established +``` diff --git a/docs/content/development/cache/overview.mdx b/docs/content/development/cache/overview.mdx new file mode 100644 index 0000000000..c498071427 --- /dev/null +++ b/docs/content/development/cache/overview.mdx @@ -0,0 +1,65 @@ +--- +description: 'In this document, you’ll learn about Cache Modules in Medusa, how they work, and which modules Medusa provides.' +--- + +import DocCardList from '@theme/DocCardList'; +import DocCard from '@theme/DocCard'; +import Icons from '@theme/Icon'; + +# Cache + +In this document, you’ll learn about Cache Modules in Medusa, how they work, and which modules Medusa provides. + +## Overview + +Cache is used in Medusa to store the results of computations such as price selection or various tax calculations. The caching layer in Medusa is implemented as a [module](../modules/overview.mdx). This allows you to replace the underlying database/cache store in your project. + +Modules are packages that export a service. The main service exported from a cache module has to implement the `ICacheService` interface exported from the `@medusajs/types` package. + +During the app startup, the modules loader will load the service into the [dependency injection container](../fundamentals/dependency-injection.md) to be available as `cacheService` to the rest of the commerce application. + +--- + +## Available Modules + +Medusa default starter project comes with the in-memory cache module (`@medusajs/cache-inmemory`). In-memory cache uses a plain JS Map object to store data, which is great for testing and development purposes. + +For production environments, there is the Redis cache module package (`@medusajs/cache-redis`) that you can install. + + + +--- + +## Custom Development + +Developers can create custom cache modules to implement their custom cache logic. The module can be installed and used in any Medusa backend. + + diff --git a/docs/content/development/endpoints/add-middleware.md b/docs/content/development/endpoints/add-middleware.md index 83e3808be9..65ec72394e 100644 --- a/docs/content/development/endpoints/add-middleware.md +++ b/docs/content/development/endpoints/add-middleware.md @@ -3,7 +3,7 @@ description: 'Learn how to add a middleware in Medusa. A middleware is a functio addHowToData: true --- -# How to Add a Middleware +# Middlewares In this document, you’ll learn how to add a middleware to an existing or custom route in Medusa. @@ -19,36 +19,17 @@ You can add a middleware to an existing route in the Medusa backend, a route in --- -## Add a Middleware +## How to Add a Middleware -Adding a middleware is very similar to adding a custom endpoint. The middleware must be created either in the `src/api/index.ts` entry point, or other TypeScript or JavaScript files imported into `src/api/index.ts`. +### Step 1: Create the Middleware File -:::info +You can organize your middlewares as you see fit, but it's recommended to create Middlewares in the `src/api/middlewares` directory. It's recommended to create each middleware in a different file. -Learn more about creating custom endpoints in [this documentation](./create.md). +Each file should export a middleware function that accepts three parameters: -::: - -The following code snippet is an example of adding a middleware: - -```ts title=src/api/index.ts -import { Router } from "express" - -export default () => { - const router = Router() - - router.use("/store/products", (req, res, next) => { - // perform an action when retrieving products - next() - }) - - return router -} -``` - -This code snippet adds a middleware to the [List Products](/api/store/#tag/Product/operation/GetProducts) endpoint. In the middleware function, you can perform any action. - -Then, you must call the `next` method received as a third parameter in the middleware function to ensure that the endpoint executes after the middleware. +1. The first one is an Express request object. It can be used to get details related to the request or resolve resources from the dependency container. +2. The second one is an Express response object. It can be used to modify the response, or in some cases return a response without executing the associated endpoint. +3. The third one is a next middleware function that ensures that other middlewares and the associated endpoint are executed. :::info @@ -56,9 +37,53 @@ You can learn more about Middlewares and their capabilities in [Express’s docu ::: ---- +Here's an example of a middleware: -## Building Files +```ts title=src/api/middlewares/custom-middleware.ts +export function customMiddleware(req, res, next) { + // TODO perform an action + + next() +} +``` + +### Step 2: Apply Middleware on an Endpoint + +To apply a middleware on any endpoint, you can use the same router defined in `src/api/index.ts` or any other router that is used or exported by `src/api/index.ts`. For example: + +:::warning + +The examples used here don't apply Cross-Origin Resource Origin (CORS) options for simplicity. Make sure to apply them, especially for core routes, as explained in the [Create Endpoint](./create.md#cors-configuration) documentation. + +::: + +```ts title=src/api/index.ts +import { Router } from "express" +import { + customMiddleware, +} from "./middlewares/custom-middleware" + +export default (rootDirectory, pluginOptions) => { + const router = Router() + + // custom route + router.get("/hello", (req, res) => { + res.json({ + message: "Welcome to My Store!", + }) + }) + + // middleware for the custom route + router.use("/hello", customMiddleware) + + // middleware for core route + router.use("/store/products", customMiddleware) + + return router +} +``` + +## Step 3: Building Files Similar to custom endpoints, you must transpile the files under `src` into the `dist` directory for the backend to load them. @@ -68,6 +93,100 @@ To do that, run the following command before running the Medusa backend: npm run build ``` +You can then test that the middleware is working by running the backend. + +--- + +## Registering New Resources in Dependency Container + +In some cases, you may need to register a resource to use within your commerce application. For example, you may want to register the logged-in user to access it in other services. You can do that in your middleware. + +:::tip + +If you want to register a logged-in user and access it in your resources, you can check out [this example guide](./example-logged-in-user.md). + +::: + +To register a new resource in the dependency container, use the `req.scope.register` method: + +```ts title=src/api/middlewares/custom-middleware.ts +export function customMiddleware(req, res, next) { + // TODO perform an action + + req.scope.register({ + customResource: { + resolve: () => "my custom resource", + }, + }) + + next() +} +``` + +You can then load this new resource within other resources. For example, to load it in a service: + + + +```ts title=src/services/custom-service.ts +import { TransactionBaseService } from "@medusajs/medusa" + +class CustomService extends TransactionBaseService { + + constructor(container, options) { + super(...arguments) + + // use the registered resource. + try { + container.customResource + } catch (e) { + // avoid errors when the backend first loads + } + } +} + +export default CustomService +``` + +Notice that you have to wrap your usage of the new resource in a try-catch block when you use it in a constructor. This is to avoid errors that can arise when the backend first loads, as the resource is not registered yet. + +### Note About Services Lifetime + +If you want to access new registrations in the dependency container within a service, you must set the lifetime of the service either to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. Services that have a `Lifetime.SINGLETON` lifetime can't access new registrations since they're resolved and cached in the root dependency container beforehand. You can learn more in the [Create Services documentation](../services/create-service.md#service-life-time). + +For custom services, no additional action is required as the default lifetime is `Lifetime.TRANSIENT`. However, if you extend a core service, you must change the lifetime since the default lifetime for core services is `Lifetime.SINGLETON`. + +For example: + + + +```ts +import { Lifetime } from "awilix" +import { + ProductService as MedusaProductService, +} from "@medusajs/medusa" + +// extending ProductService from the core +class ProductService extends MedusaProductService { + // The default life time for a core service is SINGLETON + static LIFE_TIME = Lifetime.SCOPED + + constructor(container, options) { + super(...arguments) + + // use the registered resource. + try { + container.customResource + } catch (e) { + // avoid errors when the backend first loads + } + } + + // ... +} + +export default ProductService +``` + --- ## See Also diff --git a/docs/content/development/endpoints/example-logged-in-user.md b/docs/content/development/endpoints/example-logged-in-user.md new file mode 100644 index 0000000000..7faedb1c9a --- /dev/null +++ b/docs/content/development/endpoints/example-logged-in-user.md @@ -0,0 +1,193 @@ +--- +description: 'In this document, you’ll see an example of how you can use middlewares and endpoints to register the logged-in user in the dependency container of your commerce application.' +addHowToData: true +--- + +# Example: Access Logged-In User + +In this document, you’ll see an example of how you can use middlewares and endpoints to register the logged-in user in the dependency container of your commerce application. You can then access the logged-in user in other resources, such as services. + +This guide showcases how to register the logged-in admin user, but you can apply the same steps if you want to register the current customer. + +This documentation does not explain the basics of [middlewares](./add-middleware.md) and [endpoints](./create.md). You can refer to their respective guides for more details about each. + +## Step 1: Create the Middleware + +Create the file `src/api/middlewareds/logged-in-user.ts` with the following content: + +```ts title=src/api/middlewareds/logged-in-user.ts +import { User, UserService } from "@medusajs/medusa" + +export async function registerLoggedInUser(req, res, next) { + let loggedInUser: User | null = null + + if (req.user && req.user.userId) { + const userService = + req.scope.resolve("userService") as UserService + loggedInUser = await userService.retrieve(req.user.userId) + } + + req.scope.register({ + loggedInUser: { + resolve: () => loggedInUser, + }, + }) + + next() +} +``` + +This retrieves the ID of the current user to retrieve an instance of it, then registers it in the scope under the name `loggedInUser`. + +--- + +## Step 2: Apply Middleware on Endpoint + +Create the file `src/api/routes/create-product.ts` with the following content: + +```ts title=src/api/routes/create-product.ts +import cors from "cors" +import { Router } from "express" +import { + registerLoggedInUser, +} from "../middlewares/logged-in-user" +import + authenticate +from "@medusajs/medusa/dist/api/middlewares/authenticate" + +const router = Router() + +export default function (adminCorsOptions) { + // This router will be applied before the core routes. + // Therefore, the middleware will be executed + // before the create product handler is hit + router.use( + "/admin/products", + cors(adminCorsOptions), + authenticate(), + registerLoggedInUser + ) + return router +} +``` + +In the example above, the middleware is applied on the `/admin/products` core endpoint. However, you can apply it on any other endpoint. You can also apply it to custom endpoints. + +For endpoints that require Cross-Origin Resource Origin (CORS) options, such as core endpoints, you must pass the CORS options to the middleware as well since it will be executed before the underlying endpoint. + +:::tip + +In the above code snippet, the `authenticate` middleware imported from `@medusajs/medusa` is used to ensure that the user is logged in first. If you're implementing this for middleware to register the logged-in customer, make sure to use the [customer's authenticate middleware](./create.md#protect-store-routes). + +::: + +--- + +## Step 3: Register Endpoint in the API + +Create the file `src/api/index.ts` with the following content: + +```ts title=src/api/index.ts +import configLoader from "@medusajs/medusa/dist/loaders/config" +import createProductRouter from "./routes/create-product" + +export default async function (rootDirectory: string) { + const config = await configLoader(rootDirectory) + + const adminCors = { + origin: config.projectConfig.admin_cors.split(","), + credentials: true, + } + + const productRouters = [ + createProductRouter(adminCors), + ] + + return [...productRouters] +} +``` + +This exports an array of endpoints, one of them being the product endpoint that you applied the middleware on in the second step. You can export more endpoints as well. + +--- + +## Step 4: Use in a Service + +You can now access the logged-in user in a service. For example, to access it in a custom service: + + + +```ts +import { Lifetime } from "awilix" +import { + TransactionBaseService, + User, +} from "@medusajs/medusa" + +class HelloService extends TransactionBaseService { + + protected readonly loggedInUser_: User | null + + constructor(container, options) { + super(...arguments) + + try { + this.loggedInUser_ = container.loggedInUser + } catch (e) { + // avoid errors when backend first runs + } + } + + // ... +} + +export default HelloService +``` + +If you're accessing it in an extended core service, it’s important to change the lifetime of the service to `Lifetime.SCOPED`. For example: + + + +```ts +import { Lifetime } from "awilix" +import { + ProductService as MedusaProductService, + User, +} from "@medusajs/medusa" + +// extend core product service +class ProductService extends MedusaProductService { + // The default life time for a core service is SINGLETON + static LIFE_TIME = Lifetime.SCOPED + + protected readonly loggedInUser_: User | null + + constructor(container, options) { + super(...arguments) + + this.loggedInUser_ = container.loggedInUser + } +} + +export default ProductService +``` + +You can learn more about the importance of changing the service lifetime in the [Middlewares documentation](./add-middleware.md#note-about-services-lifetime). + +--- + +## Step 5: Test it Out + +To test out your implementation, run the following command in the root directory of the Medusa backend to transpile your changes: + +```bash npm2yarn +npm run build +``` + +Then, run your backend with the following command: + +```bash npm2yarn +npm run start +``` + +If you try accessing the endpoints you added the middleware to, you should see your implementation working as expected. \ No newline at end of file diff --git a/docs/content/development/entities/create.md b/docs/content/development/entities/create.md index 4714bcc9d5..4d0b08ffa5 100644 --- a/docs/content/development/entities/create.md +++ b/docs/content/development/entities/create.md @@ -3,11 +3,11 @@ description: 'Learn how to create an entity in Medusa. This guide also explains addHowToData: true --- -# Create an Entity +# How to Create an Entity -In this document, you’ll learn how you can create an [Entity](./overview.mdx). +In this document, you’ll learn how you can create a custom [Entity](./overview.mdx). -## Create the Entity +## Step 1: Create the Entity To create an entity, create a TypeScript file in `src/models`. For example, here’s a `Post` entity defined in the file `src/models/post.ts`: @@ -52,46 +52,85 @@ export class Post extends SoftDeletableEntity { You can learn more about what decorators and column types you can use in [Typeorm’s documentation](https://typeorm.io/entities). -### Create a Migration +--- + +## Step 2: Create a Migration Additionally, you must create a migration for your entity. Migrations are used to update the database schema with new tables or changes to existing tables. -You can learn more about Migrations, how to create them, and how to run them in the [Migration documentation](./migrations/overview.mdx). +You can learn more about Migrations, how to create or generate them, and how to run them in the [Migration documentation](./migrations/overview.mdx). -### Create a Repository +--- + +## Step 3: Create a Repository Entities data can be easily accessed and modified using Typeorm [Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` created in `src/repositories/post.ts`: ```ts title=src/repositories/post.ts -import { EntityRepository, Repository } from "typeorm" - import { Post } from "../models/post" +import { + dataSource, +} from "@medusajs/medusa/dist/loaders/database" -@EntityRepository(Post) -export class PostRepository extends Repository { } +export const PostRepository = dataSource + .getRepository(Post) + +export default PostRepository ``` -This repository is created for the `Post` and that is indicated using the decorator `@EntityRepository`. +The repository is created using the `getRepository` method of the data source exported from the core package in Medusa. This method accepts the entity as a parameter. :::tip -Be careful with your file names as it can cause unclear errors in Typeorm. Make sure all your file names are small letters for both entities and repositories to avoid any issues with file names. +A data source is Typeorm’s connection settings that allows you to connect to your database. You can learn more about it in [Typeorm’s documentation](https://typeorm.io/data-source). ::: +If you want to add methods to that repository or override Typeorm's Repository methods, you can do that using the `extend` method: + +```ts title=src/repositories/post.ts +import { Post } from "../models/post" +import { + dataSource, +} from "@medusajs/medusa/dist/loaders/database" + +export const PostRepository = dataSource + .getRepository(Post) + .extend({ + customFunction(): void { + // TODO add custom implementation + return + }, + }) + +export default PostRepository +``` + +You can learn about available Repository methods in [Typeorm's documentation](https://typeorm.io/repository-api). + --- -## Access a Custom Entity +## Step 4: Run Migrations -:::note +Before you start using your entity, make sure to run the migrations that reflect the entity on your database schema. -Before trying this step make sure that you’ve created and run your migrations. You also need to re-build your code using: +To do that, run the `build` command that transpiles your code: ```bash npm2yarn npm run build ``` -::: +Then, run the `migration` command: + +```bash npm2yarn +medusa migrations run +``` + +You should see that your migration have executed. + +--- + +## Step 5: Use Your Entity You can access your custom entity data in the database in services or subscribers using the repository. For example, here’s a service that lists all posts: @@ -107,9 +146,9 @@ class PostService extends TransactionBaseService { } async list() { - const postRepository = this.manager_ - .getCustomRepository(this.postRepository) - return await postRepository.find() + const postRepo = this.manager_ + .withRepository(this.postRepository) + return await postRepo.find() } } @@ -118,15 +157,13 @@ export default PostService In the constructor, you can use dependency injection to get access to instances of services and repositories. Here, you initialize class fields `postRepository` and `manager`. The `manager` is a [Typeorm Entity Manager](https://typeorm.io/working-with-entity-manager). -Then, in the method `list`, you can obtain an instance of the `PostRepository` using `this.manager_.getCustomRepository` passing it `this.postRepository` as a parameter. This lets you use [Custom Repositories with Typeorm](https://typeorm.io/custom-repository) to create custom methods in your repository that work with the data in your database. +Then, in the method `list`, you can create an instance of the `PostRepository` using the `this.manager_.withRepository` method passing it `this.postRepository` as a parameter. -After getting an instance of the repository, you can then use [Typeorm’s Repository methods](https://typeorm.io/repository-api) to perform Create, Read, Update, and Delete (CRUD) operations on your entity. - -If you need access to your entity in endpoints, you can then use the methods you define in the service. +After getting an instance of the repository, you can then use [Typeorm’s Repository methods](https://typeorm.io/repository-api) to perform Create, Read, Update, and Delete (CRUD) operations on your entity. You can also use any custom methods that you defined in the Repository. :::note -This same usage of repositories can be done in subscribers as well. +This same usage of repositories can be done in other resources such as subscribers or endpoints. ::: @@ -142,5 +179,5 @@ await postRepository.softDelete(post.id) ## See Also -- [Migrations Overview](./migrations/overview.mdx) +- [Extend Entity](./extend-entity.md) - [Create a Plugin](../plugins/create.md) diff --git a/docs/content/development/entities/extend-entity.md b/docs/content/development/entities/extend-entity.md new file mode 100644 index 0000000000..2ceae43ff2 --- /dev/null +++ b/docs/content/development/entities/extend-entity.md @@ -0,0 +1,158 @@ +--- +description: 'Learn how to extend a core entity in Medusa to add custom attributes.' +addHowToData: true +--- + +# How to Extend an Entity + +In this document, you’ll learn how to extend a core entity in Medusa. + +## Overview + +Medusa uses entities to represent tables in the database. As you build your custom commerce application, you’ll often need to add your own properties to those entities. This guide explains the necessary steps to extend core Medusa entities. + +This guide will use the Product entity as an example to demonstrate the steps. + +### Word of Caution about Overriding + +Extending entities to add new attributes or methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods or attributes, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version. + +--- + +## Step 1: Create Entity File + +In your Medusa backend, create the file `src/models/product.ts`. This file will hold your extended entity. + +Note that the name of the file must be the same as the name of the original entity in the core package. Since in this guide you’re overriding the Product entity, it’s named `product` to match the core. If you’re extending the customer entity, for example, the file should be named `customer.ts`. + +--- + +## Step 2: Implement Extended Entity + +In the file you created, you can import the entity you’re extending from the core package, then create a class that extends that entity. You can add in that class the new attributes and methods. + +Here’s an example of extending the Product entity: + +```ts title=src/models/product.ts +import { Column, Entity } from "typeorm" +import { + // alias the core entity to not cause a naming conflict + Product as MedusaProduct, +} from "@medusajs/medusa" + +@Entity() +export class Product extends MedusaProduct { + @Column() + customAttribute: string +} +``` + +--- + +## (Optional) Step 3: Create a TypeScript Declaration File + +If you’re using JavaScript instead of TypeScript in your implementation, you can skip this step. + +To ensure that TypeScript is aware of your extended entity and affects the typing of the Medusa package itself, create the file `src/index.d.ts` with the following content: + +```ts title=src/index.d.ts +export declare module "@medusajs/medusa/dist/models/product" { + declare interface Product { + customAttribute: string; + } +} +``` + +Notice that you must pass the attributes you added to the entity into the `interface`. The attributes will be merged with the attributes defined in the core `Product` entity. + +--- + +## Step 4: Extend Repository + +As the entity is used throughout the commerce application through its repository, the core package will not actually be aware that the entity was extended unless you also extend the repository. + +The steps here are similar to those described in the [How to Extend a Repository documentation](./extend-repository.md), however, the implementation is a little different. + +Start by creating the repository file `src/repositories/product.ts`. As mentioned in the repository documentation, the name of the file should be the same as the name in the core. So, if you’re extending another repository, use the file name of that repository instead. + +Then, in the file, add the following content: + +```ts title=src/repositories/product.ts +import { Product } from "../models/product" +import { + dataSource, +} from "@medusajs/medusa/dist/loaders/database" +import { + // alias the core repository to not cause a naming conflict + ProductRepository as MedusaProductRepository, +} from "@medusajs/medusa/dist/repositories/product" + +export const ProductRepository = dataSource + .getRepository(Product) + .extend({ + // it is important to spread the existing repository here. + // Otherwise you will end up losing core properties. + // you also update the target to the extended entity + ...Object.assign( + MedusaProductRepository, + { target: Product } + ), + + // you can add other customizations as well... + }) + +export default ProductRepository +``` + +Instead of just spreading the properties of the `MedusaProductRepository` as you did when extending a repository, you have to change the value of the `target` property to be the entity you created. + +--- + +## Step 5: Create Migration + +To reflect your entity changes on the database schema, you must create a migration with those changes. + +You can learn how to create or generate a migration in [this documentation](./migrations/create.md). + +Here’s an example of a migration of the entity extended in this guide: + +```ts title=src/migration/1680013376180-changeProduct.ts +import { MigrationInterface, QueryRunner } from "typeorm" + +class changeProduct1680013376180 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE \"product\"" + + " ADD COLUMN \"customAttribute\" text" + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE \"product\" DROP COLUMN \"customAttribute\"" + ) + } +} + +export default changeProduct1680013376180 +``` + +--- + +## Step 6: Use Custom Entity + +For changes to take effect, you must transpile your code by running the `build` command in the root of the Medusa backend: + +```bash npm2yarn +npm run build +``` + +Then, run the following command to migrate your changes to the database: + +```bash npm2yarn +medusa migrations run +``` + +You should see that your migration was executed, which means your changes were reflected in the database schema. + +You can now use your extended entity and its repository throughout your commerce application. diff --git a/docs/content/development/entities/extend-repository.md b/docs/content/development/entities/extend-repository.md new file mode 100644 index 0000000000..9d30e334a1 --- /dev/null +++ b/docs/content/development/entities/extend-repository.md @@ -0,0 +1,131 @@ +--- +description: 'Learn how to extend a core repository in Medusa to add custom methods.' +addHowToData: true +--- + +# How to Extend a Repository + +In this document, you’ll learn how to extend a core repository in Medusa. + +## Overview + +Medusa uses Typeorm’s Repositories to perform operations on an entity, such as retrieve or update the entity. Typeorm already provides these basic functionalities within a repository, but sometimes you need to implement a custom implementation to handle the logic behind these operations differently. You might also want to add custom methods related to processing entities that aren’t available in the default repositories. + +In this guide, you’ll learn how to extend a repository in the core Medusa package. This guide will use the Product repository as an example to demonstrate the steps. + +### Word of Caution about Overriding + +Extending repositories to add new methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version. + +--- + +## Step 1: Create Repository File + +In your Medusa backend, create the file `src/repositories/product.ts`. This file will hold your extended repository. + +Note that the name of the file must be the same as the name of the original repository in the core package. Since in this guide you’re extending the Product repository, it’s named `product` to match the core. If you’re extending the customer repository, for example, the file should be named `customer.ts`. + +--- + +## Step 2: Implement Extended Repository + +In the file you created, you must retrieve both the repository you're extending along with its entity from the core. You’ll then use the data source exported from the core package to extend the repository. + +:::tip + +A data source is Typeorm’s connection settings that allows you to connect to your database. You can learn more about it in [Typeorm’s documentation](https://typeorm.io/data-source). + +::: + +Here’s an example of the implementation of the extended Product repository: + +```ts title=src/repositories/product.ts +import { Product } from "@medusajs/medusa" +import { + dataSource, +} from "@medusajs/medusa/dist/loaders/database" +import { + // alias the core repository to not cause a naming conflict + ProductRepository as MedusaProductRepository, +} from "@medusajs/medusa/dist/repositories/product" + +export const ProductRepository = dataSource + .getRepository(Product) + .extend({ + // it is important to spread the existing repository here. + // Otherwise you will end up losing core properties + ...MedusaProductRepository, + + /** + * Here you can create your custom function + * For example + */ + customFunction(): void { + // TODO add custom implementation + return + }, + }) + +export default ProductRepository +``` + +You first import all necessary resources from the core package: the `Product` entity, the `dataSource` instance, and the core’s `ProductRepository` aliased as `MedusaProductRepository` to avoid naming conflict. + +You then use the `dataSource` instance to retrieve the `Product` entity’s repository and extend it using the repository’s `extend` method. This method is available as part of Typeorm Repository API. This method returns your extended repository. + +The `extend` method accepts an object with all the methods to add to the extended repository. + +You must first add the properties of the repository you’re extending, which in this case is the product repository (aliased as `MedusaProductRepository`). This will ensure you don’t lose core methods, which can lead to the core not working as expected. You add use the spread operator (`…`) with the `MedusaProductRepository` to spread its properties. + +After that, you can add your custom methods to the repository. In the example above, you add the method `customFunction`. You can use any name for your methods. + +--- + +## Step 3: Use Your Extended Repository + +You can now use your extended repository in other resources such as services or endpoints. + +Here’s an example of using it in an endpoint: + +```ts +import ProductRepository from "./path/to/product.ts" +import EntityManager from "@medusajs/medusa" + +export default () => { + // ... + + router.get("/custom-endpoint", (req, res) => { + // ... + + const productRepository: typeof ProductRepository = + req.scope.resolve( + "productRepository" + ) + const manager: EntityManager = req.scope.resolve("manager") + const productRepo = manager.withRepository( + productRepository + ) + productRepo.customFunction() + + // ... + }) +} +``` + +--- + +## Step 4: Test Your Implementation + +For changes to take effect, you must transpile your code by running the `build` command in the root of the Medusa backend: + +```bash npm2yarn +npm run build +``` + +Then, run the following command to start your backend: + +```bash npm2yarn +npm run start +``` + +You should see your custom implementation working as expected. diff --git a/docs/content/development/entities/migrations/create.md b/docs/content/development/entities/migrations/create.md index 6ccf249126..362580995f 100644 --- a/docs/content/development/entities/migrations/create.md +++ b/docs/content/development/entities/migrations/create.md @@ -22,36 +22,29 @@ The migration file must be inside the `src/migrations` directory. When you run t
Generating Migrations for Entities - You can alternatively use Typeorm's `generate` command to generate a Migration file from existing entity classes. As Medusa uses v0.2.45 of Typeorm, you have to create a `ormconfig.json` first before using the `generate` command. + You can alternatively use Typeorm's `generate` command to generate a Migration file from existing entity classes. As of v1.8, Medusa uses Typeorm v0.3.x. You have to create a [DataSource](https://typeorm.io/data-source) first before using the `migration:generate` command. -:::note + For example, create the file `datasource.js` in the root of your Medusa server with the following content: -Typeorm will be updated to the latest version in v1.8.0 of Medusa. - -::: - - For example, create the file `ormconfig.json` in the root of your Medusa server with the following content: - - ```json - { - "type": "postgres", - "host": "localhost", - "port": 5432, - "username": "", - "password": "", - "database": "", - "synchronize": true, - "logging": false, - "entities": [ - "dist/models/**/*.js" + ```js + const { DataSource } = require("typeorm") + + const AppDataSource = new DataSource({ + type: "postgres", + port: 5432, + username: "", + password: "", + database: "", + entities: [ + "dist/models/*.js", ], - "migrations": [ - "dist/migrations/**/*.js" + migrations: [ + "dist/migrations/*.js", ], - "cli": { - "entitiesDir": "src/models", - "migrationsDir": "src/migrations" - } + }) + + module.exports = { + datasource: AppDataSource, } ``` @@ -66,7 +59,7 @@ Typeorm will be updated to the latest version in v1.8.0 of Medusa. Finally, run the following command to generate a Migration for your new entity: ```bash - npx typeorm@0.2.45 migration:generate -n PostCreate + npx typeorm migration:generate -d datasource.js src/migrations/PostCreate ``` Where `PostCreate` is just an example of the name of the migration to generate. The migration will then be generated in `src/migrations/-PostCreate.ts`. You can then skip to step 3 of this guide. diff --git a/docs/content/development/entities/overview.mdx b/docs/content/development/entities/overview.mdx index 1d3b095ae6..3a2ada2217 100644 --- a/docs/content/development/entities/overview.mdx +++ b/docs/content/development/entities/overview.mdx @@ -76,10 +76,10 @@ Developers can create custom entities in the Medusa backend, a plugin, or in a C { type: 'link', href: '/development/entities/create', - label: 'Create an Endpoint', + label: 'Create an Entity', customProps: { icon: Icons['academic-cap-solid'], - description: 'Learn how to create endpoints in Medusa.' + description: 'Learn how to create an entity in Medusa.' } }, { @@ -91,4 +91,22 @@ Developers can create custom entities in the Medusa backend, a plugin, or in a C description: 'Learn how to create migrations in Medusa.' } }, + { + type: 'link', + href: '/development/entities/extend-entity', + label: 'Extend an Entity', + customProps: { + icon: Icons['academic-cap-solid'], + description: 'Learn how to extend a core Medusa entity.' + } + }, + { + type: 'link', + href: '/development/entities/extend-repository', + label: 'Extend a Repository', + customProps: { + icon: Icons['academic-cap-solid'], + description: 'Learn how to extend a core Medusa repository.' + } + }, ]} /> \ No newline at end of file diff --git a/docs/content/development/events/create-module.md b/docs/content/development/events/create-module.md new file mode 100644 index 0000000000..7e878a58f9 --- /dev/null +++ b/docs/content/development/events/create-module.md @@ -0,0 +1,260 @@ +--- +description: 'In this document, you’ll learn how to create an events module, then test and publish the module as an NPM package.' +addHowToData: true +--- + +# Create Events Module + +In this document, you’ll learn how to create an events module. + +## Overview + +Medusa provides ready-made modules for events, including the local and Redis modules. If you prefer another technology used for managing events, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances. + +In this document, you’ll learn how to build your own Medusa events module, mainly focusing on creating the event bus service and the available methods you need to implement within your module. + +--- + +## (Optional) Step 0: Prepare Module Directory + +Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation. + +You can refer to the [Project Preparation step in the Create Module documentation](../modules/create.mdx#optional-step-0-project-preparation) to learn how to do that. + +--- + +## Step 1: Create the Service + +Create the file `services/event-bus-custom.ts` which will hold your event bus service. Note that the name of the file is recommended to be in the format `event-bus-` where `` is the name of the service you’re integrating. For example, `event-bus-redis`. + +Add the following content to the file: + +```ts title=services/event-bus-custom.ts +import { EmitData, EventBusTypes } from "@medusajs/types" +import { AbstractEventBusModuleService } from "@medusajs/utils" + +class CustomEventBus extends AbstractEventBusModuleService { + async emit( + eventName: string, + data: T, + options: Record + ): Promise; + async emit(data: EmitData[]): Promise; + async emit( + eventName: unknown, + data?: unknown, + options?: unknown + ): Promise { + throw new Error("Method not implemented.") + } +} + +export default CustomEventBus +``` + +This creates the class `CustomEventBus` that implements the `AbstractEventBusModuleService` class imported from `@medusajs/utils`. Feel free to rename the class to what’s relevant for your event bus service. + +In the class you must implement the `emit` method. You can optionally implement the `subscribe` and `unsubscribe` methods. + +--- + +## Step 2: Implement Methods + +### Note About the eventToSubscribersMap Property + +The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler methods are managed within a class property `eventToSubscribersMap`, which is a JavaScript Map. They map keys are the event names, whereas the value of each key is an array of subscribed handler methods. + +In your custom implementation, you can use this property to manage the subscribed handler methods. For example, you can get the subscribers of a method using the `get` method of the map: + +```ts +const eventSubscribers = + this.eventToSubscribersMap.get(eventName) || [] +``` + +Alternatively, you can implement custom logic for the `subscribe` and `unsubscribe` events, which is explained later in this guide. + +### constructor + +The `constructor` method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the module’s options which are typically defined in `medusa-config.js`, and to other services and resources in the Medusa backend using [dependency injection](../fundamentals/dependency-injection.md). + +Here’s an example of how you can use the `constructor` to store the options of your module: + + + +```ts title=services/event-bus-custom.ts +class CustomEventBus extends AbstractEventBusModuleService { + protected readonly moduleOptions: Record + + constructor({ + // inject resources from the Medusa backend + // for example, you can inject the logger + logger, + }, options) { + super(...arguments) + this.moduleOptions = options + } + + // ... +} +``` + +### emit + +The `emit` method is used to push an event from Medusa into your messaging system. Typically, the subscribers to that event would then pick up the message and execute their asynchronous tasks. + +The `emit` method has two different signatures: + +1. The first signature accepts three parameters. The first parameter is `eventName` being a required string indicating the name of the event to trigger. The second parameter is `data` being the optional data to send to subscribers of that event. The third optional parameter `options` which can be used to pass options specific to the event bus. +2. The second signature accepts one parameter, which is an array of objects having three properties: `eventName`, `data`, and `options`. These are the same as the parameters that can be passed in the first signature. This signature allows emitting more than one event. + +The `options` parameter depends on the event bus integrating. For example, the Redis event bus accept the following options: + +```ts title=services/event-bus-custom.ts +type JobData = { + eventName: string + data: T + completedSubscriberIds?: string[] | undefined +} +``` + +You can implement your method in a way that supports both signatures by checking the type of the first input. For example: + +```ts title=services/event-bus-custom.ts +class CustomEventBus extends AbstractEventBusModuleService { + // ... + async emit( + eventName: string, + data: T, + options: Record + ): Promise; + async emit(data: EmitData[]): Promise; + async emit( + eventOrData: string | EmitData[], + data?: T, + options: Record = {} + ): Promise { + const isBulkEmit = Array.isArray(eventOrData) + + // emit event + } +} +``` + +### (optional) subscribe + +As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom subscribe logic if necessary. + +The `subscribe` method attaches a handler method to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class. + +The `subscribe` method accepts three parameters: + +1. The first parameter `eventName` is a required string. It indicates which event the handler method is subscribing to. +2. The second parameter `subscriber` is a required function that performs an action when the event is triggered. +3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler methods when retrying a failed method. It’s also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method. + +The implementation of this method depends on the service you’re using for the event bus: + +```ts title=services/event-bus-custom.ts +class CustomEventBus extends AbstractEventBusModuleService { + // ... + subscribe( + eventName: string | symbol, + subscriber: EventBusTypes.Subscriber, + context?: EventBusTypes.SubscriberContext): this { + // TODO implement subscription + } +} +``` + +### (optional) unsubscribe + +As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom unsubscribe logic if necessary. + +The `unsubscribe` method is used to unsubscribe a handler method from an event. + +The `unsubscribe` method accepts three parameters: + +1. The first parameter `eventName` is a required string. It indicates which event the handler method is unsubscribing from. +2. The second parameter `subscriber` is a required function that was initially subscribed to the event. +3. The third parameter `context` is an optional object that has the property `subscriberId`. It can be used to specify the ID of the subscriber to unsubscribe. + +The implementation of this method depends on the service you’re using for the event bus: + +```ts title=services/event-bus-custom.ts +class CustomEventBus extends AbstractEventBusModuleService { + // ... + unsubscribe( + eventName: string | symbol, + subscriber: EventBusTypes.Subscriber, + context?: EventBusTypes.SubscriberContext): this { + // TODO implement subscription + } +} +``` + +--- + +## Step 3: Export the Service + +After implementing the event bus service, you must export it so that the Medusa backend can use it. + +Create the file `index.ts` with the following content: + +```ts title=services/event-bus-custom.ts +import { ModuleExports } from "@medusajs/modules-sdk" + +import { CustomEventBus } from "./services" + +const service = CustomEventBus + +const moduleDefinition: ModuleExports = { + service, +} + +export default moduleDefinition +``` + +This exports a module definition, which requires at least a `service`. If you named your service something other than `CustomEventBus`, make sure to replace it with that. + +You can learn more about what other properties you can export in your module definition in the [Create a Module documentation](../modules/create.mdx#step-2-export-module). + +--- + +## Step 4: Test your Module + +You can test your module in the Medusa backend by referencing it in the configurations. + +To do that, add the module to the exported configuration in `medusa-config.js` as follows: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + cacheService: { + resolve: "path/to/custom-module", + options: { + // any necessary options + }, + }, + }, +} +``` + +Make sure to replace the `path/to/custom-module` with a relative path from your Medusa backend to your module. You can learn more about module reference in the [Create Module documentation](../modules/create.mdx#module-reference). + +You can also add any necessary options to the module. + +Then, to test the module, run the Medusa backend which also runs your module: + +```bash npm2yarn +npm run start +``` + +--- + +## (Optional) Step 5: Publish your Module + +You can publish your events module to NPM. This can be useful if you want to reuse your module across Medusa backend instances, or want to allow other developers to use it. + +You can refer to the [Publish Module documentation](../modules/publish.md) to learn how to publish your module. diff --git a/docs/content/development/events/create-subscriber.md b/docs/content/development/events/create-subscriber.md index 5d6f294872..a28913ddbd 100644 --- a/docs/content/development/events/create-subscriber.md +++ b/docs/content/development/events/create-subscriber.md @@ -7,14 +7,6 @@ addHowToData: true In this document, you’ll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action. -## Prerequisites - -Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis, so it's required for subscribers to work. - -You can learn how to [install Redis](../backend/prepare-environment.mdx#redis) and [configure it with Medusa](../backend/configurations.md#redis) before you get started. - ---- - ## Implementation A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convension, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`. @@ -41,12 +33,25 @@ export default OrderNotifierSubscriber This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need. -:::note +:::tip -The `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`. +For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`. ::: +### Subscriber ID + +The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one. +If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID. + +An example of using the subscribe method with the third parameter: + +```ts +eventBusService.subscribe("order.placed", this.handleOrder, { + subscriberId: "my-unique-subscriber", +}) +``` + --- ## Using Services in Subscribers @@ -55,7 +60,7 @@ You can access any service through the dependencies injected to your subscriber For example: -```ts +```ts title=src/subscribers/orderNotifier.ts class OrderNotifierSubscriber { constructor({ productService, eventBusService }) { this.productService = productService @@ -71,7 +76,7 @@ class OrderNotifierSubscriber { You can then use `this.productService` anywhere in your subscriber’s methods. For example: -```ts +```ts title=src/subscribers/orderNotifier.ts class OrderNotifierSubscriber { // ... handleOrder = async (data) => { diff --git a/docs/content/development/events/events-list.md b/docs/content/development/events/events-list.md index 5556e0c966..1554c7296c 100644 --- a/docs/content/development/events/events-list.md +++ b/docs/content/development/events/events-list.md @@ -826,6 +826,193 @@ Object of the following format: --- +## Inventory Item Events + +This section holds all events related to inventory items. + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Event Name + +Description + +Event Data Payload +
+ +`inventory-item.created` + + + +Triggered when an inventory item is created. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the inventory item +} +``` + +
+ +`inventory-item.updated` + + + +Triggered when an inventory item is updated. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the inventory item +} +``` + +
+ +`inventory-item.deleted` + + + +Triggered when an inventory item is deleted. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the inventory item +} +``` + +
+ +--- + +## Inventory Level Events + +This section holds all events related to inventory levels. + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Event Name + +Description + +Event Data Payload +
+ +`inventory-level.created` + + + +Triggered when an inventory level is created. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the inventory level +} +``` + +
+ +`inventory-level.updated` + + + +Triggered when an inventory level is updated. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the inventory level +} +``` + +
+ +`inventory-level.deleted` + + + +Triggered when an inventory level is deleted, which can be done either directly using its ID or based on the ID of a location. The returned ID depends on how the inventory level was deleted. + + + +Object of the following format: + +```js noReport noCopy +{ + id // (optional) string ID of the inventory level, available if it was deleted directly + location_id // (optional) string ID of location, available if level was deleted by location ID +} +``` + +
+ +--- + ## Invite Events This section holds all events related to invites. @@ -1817,7 +2004,7 @@ Triggered when the capturing of a payment fails. The entire payment passed as an object. You can refer to the [Payment entity](../../references/entities/classes/Payment.md) for an idea of what fields to expect. -In addition, an error object is passed within the same object as the Payment provider: +In addition, an error object is passed within the same object as the Payment Processor: ```js noReport noCopy { @@ -2076,6 +2263,12 @@ Object of the following format: This section holds all events related to product categories. +:::note + +Product Category feature is currently in beta mode and guarded by a feature flag. You can learn how to enable it in the [Product Categories documentation](../../modules/products/categories.md). + +::: + @@ -2438,6 +2631,101 @@ Object of the following format: --- +## Reservation Item Events + +This section holds all events related to reservation items. + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+Event Name + +Description + +Event Data Payload +
+ +`reservation-item.created` + + + +Triggered when a reservation item is created. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the reservation item +} +``` + +
+ +`reservation-item.updated` + + + +Triggered when an reservation item is updated. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the reservation item +} +``` + +
+ +`reservation-item.deleted` + + + +Triggered when a reservation item is deleted, which can be done either directly using its ID or based on the ID of a location or a line item. The returned ID depends on how the reservation item was deleted. + + + +Object of the following format: + +```js noReport noCopy +{ + id // (optional) string ID of the reservation item, available if it was deleted directly + location_id // (optional) string ID of location, available if item was deleted by location ID + line_item_id // (optional) string ID of line item, available if reservation item was deleted by line item ID +} +``` + +
+ +--- + ## Sales Channel Events This section holds all events related to sales channels. @@ -2533,6 +2821,99 @@ Object of the following format: --- +## Stock Location Events + +This section holds all events related to stock locations. + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Event Name + +Description + +Event Data Payload +
+ +`stock-location.created` + + + +Triggered when a stock location is created. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the stock location +} +``` + +
+ +`stock-location.updated` + + + +Triggered when an stock location is updated. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the stock location +} +``` + +
+ +`stock-location.deleted` + + + +Triggered when a stock location is deleted. + + + +Object of the following format: + +```js noReport noCopy +{ + id // string ID of the stock location +} +``` + +
+ +--- + ## Swap Events This section holds all events related to swaps. @@ -2958,6 +3339,6 @@ Object of the following format: ## See Also -- [Events architecture overview](./index.md) +- [Events overview](./index.mdx) - [Use services in subscribers](./create-subscriber.md#using-services-in-subscribers) - [Create a notification provider](../notification/overview.mdx) diff --git a/docs/content/development/events/index.md b/docs/content/development/events/index.md deleted file mode 100644 index 428112c402..0000000000 --- a/docs/content/development/events/index.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -description: 'Learn how the events system is implemented in Medusa. It is built on a publish-subscribe architecture. The Medusa core publishes events when certain actions take place.' ---- - -# Events Architecture - -In this document, you'll learn how the events system is implemented in Medusa. - -## Overview - -The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publishes events when certain actions take place. - -Those events can be subscribed to using subscribers. When you subscribe to an event, you can perform a task asynchronusly every time the event is triggered. - -:::info - -You can learn more about subscribers and their use cases in the [Subscribers](./subscribers.mdx) documentation. - -::: - ---- - -## Publishing and Subscribing - -The `EventBusService` is responsible for publishing and processing events. - -:::note - -The current implementation of the `EventBusService` is powered by Redis. However, an upcoming version of Medusa introduces an event bus module. This will allow you to use any publishing and subscribing provider. That will not change the general purpose and flow of the `EventBusService`. - -::: - -The `EventBusService` exposes two methods in its public API for event processing; `emit` and `subscribe`. - -### emit - -The `emit` method accepts as a first parameter the event name. It adds it to a Bull queue (powered by Redis) as a job, and processes it asynchronously. - -The second parameter contains any data that should be emitted with the event. Subscribers that handle the event will receive that data as a method parameter. - -The third parameter is an options object. It accepts options related to the number of retries if a subscriber handling the event fails, the delay time, and more. The options are explained in a [later section](#retrying-handlers) - -The `emit` method has the following signature: - -```ts -export default class EventBusService { - // ... - async emit( - eventName: string, - data: T, - options: Record & - EmitOptions = { attempts: 1 } - ): Promise -} -``` - -Here's an example of how you can emit an event using the `EventBusService`: - -```ts -eventBusService.emit( - "product.created", - { id: "prod_..." }, - { attempts: 2 } -) -``` - -The `EventBusService` emits the event `product.created` by passing the event name as a first argument. An object is passed as a second argument which is the data passed to the event handler methods in subscribers. This object contains the ID of the product. - -Options are passed in the third argument. The `attempt` property specifies how many times the subscriber should be retried if it fails (by default it's one). - -### subscribe - -The `subscribe` method will attach a handler method to the specified event, which is run when the event is triggered. It is usually used insde a subscriber class. - -The `subscribe` method accepts the event name as the first parameter. This is the event that the handler method will attach to. - -The second parameter is the handler method that will be triggered when the event is emitted. - -The third parameter is an optional `context` parameter. It allows you to configure the ID of the handler method. - -The `subscribe` method has the following signature: - -```ts -export default class EventBusService { - // ... - subscribe( - event: string | symbol, - subscriber: Subscriber, - context?: SubscriberContext - ): this -} -``` - -Here's an example of how you can subscribe to an event using the `EventBusService`: - -```ts title=src/subscribers/my.ts -import { EventBusService } from "@medusajs/medusa" - -class MySubscriber { - constructor({ - eventBusService: EventBusService, - }) { - eventBusService.subscribe("product.created", (data) => { - // TODO handle event - console.log(data.id) - }) - } -} -``` - -In the constructor of a subscriber, you use the `EventBusService` to subscribe to the event `product.created`. In the handler method, you can perform a task every time the product is created. Notice how the handler method accepts the `data` as a parameter as explain in the previous section. - -:::note - -You can learn more about how to create a subscriber in [this documentation](./create-subscriber.md) - -::: - ---- - -## Processing Events - -In the `EventBusService` service, the `worker_` method defines the logic run for each event emitted into the queue. - -By default, all handler methods to that event are retrieved and, for each of the them, the stored data provided as a second parameter in `emit` is passed as an argument. - ---- - -## Retrying Handlers - -A handler method might fail to process an event. This could happen because it communicates with a third party service currently down or due to an error in its logic. - -In some cases, you might want to retry those failed handlers. - -As briefly explained earlier, you can pass options when emitting an event as a third argument that are used to configure how the queue worker processes your job. If you pass `attempts` upon emitting the event, the processing of a handler method is retried when it fails. - -Aside from `attempts`, there are other options to futher configure the retry mechanism: - -```ts -type EmitOptions = { - delay?: number - attempts: number - backoff?: { - type: "fixed" | "exponential" - delay: number - } -} -``` - -Here's what each of these options mean: - -- `delay`: delay the triggering of the handler methods by a number of milliseconds. -- `attempts`: the number of times a subscriber handler should be retried when it fails. -- `backoff`: the wait time between each retry - -### Note on Subscriber IDs - -If you have more than one handler methods attached to a single event, or if you have multiple backend instances running, you must pass a subscriber ID as a third parameter to the `subscribe` method. This allows the `EventBusService` to differentiate between handler methods when retrying a failed one. - -If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. - -On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID. - -An example of passing a subscriber ID: - -```ts title=src/subscribers/my.ts -import { EventBusService } from "@medusajs/medusa" - -class MySubscriber { - constructor({ - eventBusService: EventBusService, - }) { - eventBusService.subscribe( - "product.created", - (data) => { - // TODO handle event - console.log(data.id) - }, - "my-unique-subscriber") - } -} -``` - -:::info - -You can learn more about subscriber IDs in [Bull's documentation](https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueadd). - -::: - ---- - -## Database transactions - - - -Transactions in Medusa ensure atomicity, consistency, isolation, and durability, or ACID, guarantees for operations in the Medusa core. - - - -In many cases, [services](../services/overview.mdx) typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced. - -Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded. - -This rather complex logic is abstracted away from the consumers of the `EventBusService`, but here's an example of the flow when an API request is made: - -1. API request starts. -2. Transaction is initiated. -3. Service layer performs some logic. -4. Events are emitted and stored in the database for eventual processing. -5. Transaction is committed. -6. API request ends. -7. Events in the database become visible. - -To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the [Processing](#processing-events) section earlier. - -:::info - -This pattern is heavily inspired by the [Transactionally-staged Job Drain described in this blog post](https://brandur.org/job-drain). - -::: - ---- - -## See Also - -- [Events reference](./events-list.md) -- [Create a subscriber](./create-subscriber.md) \ No newline at end of file diff --git a/docs/content/development/events/index.mdx b/docs/content/development/events/index.mdx new file mode 100644 index 0000000000..5d32a97834 --- /dev/null +++ b/docs/content/development/events/index.mdx @@ -0,0 +1,131 @@ +--- +description: 'Learn how the events system is implemented in Medusa. It is built on a publish-subscribe architecture. The Medusa core publishes events when certain actions take place.' +--- + +import DocCardList from '@theme/DocCardList'; +import Icons from '@theme/Icon'; + +# Events + +In this document, you’ll learn what events are and why they’re useful in Medusa. + +## Overview + +Events are used in Medusa to inform different parts of the commerce ecosystem that this event occurred. For example, when an order is placed, the `order.placed` event is triggered, which informs notification services like SendGrid to send a confirmation email to the customer. + +The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publish an event when an action takes place, and modules, plugins, or other forms of customizations can subscribe to that event. [Subscribers](./subscribers.mdx) can then perform a task asynchronously when the event is triggered. + +Although the core implements the main logic behind the events system, you’ll need to use an event module that takes care of the publish/subscribe functionality such as subscribing and emitting events. Medusa provides modules that you can use both for development and production, including Redis and Local modules. + +--- + +## Database Transactions + +Transactions in Medusa ensure Atomicity, Consistency, Isolation, and Durability (ACID) guarantees for operations in the Medusa core. + +In many cases, services typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced. + +Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded. + +This rather complex logic is abstracted away from the consumers of the EventBusService, but here's an example of the flow when an API request is made: + +- API request starts. +- Transaction is initiated. +- Service layer performs some logic. +- Events are emitted and stored in the database for eventual processing. +- Transaction is committed. +- API request ends. +- Events in the database become visible. + +To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the Processing section earlier. + +:::note + +This pattern is heavily inspired by the Transactionally-staged Job Drain described in this blog post. + +::: + +--- + +## Emitting Events + +You can emit events in Medusa using the `EventBusService`. For example: + +```ts +this.eventBusService.emit("custom-event", { + // attach any data to the event +}) +``` + +You can also emit more than one event: + +```ts +this.eventBusService.emit([ + { + eventName: "custom-event-1", + data: { + // attach any data to the event + }, + }, + { + eventName: "custom-event-2", + data: { + // attach any data to the event + }, + }, +]) +``` + +--- + +## Available Modules + +Medusa’s default starter project comes with the local event module (`@medusajs/event-bus-local`). For production environments, it’s recommended to use the Redis event module package (`@medusajs/event-bus-redis`) that you can install. + + + +--- + +## Custom Development + +Developers can create custom event modules, allowing them to integrate any third-party services or logic to handle this functionality. Developers can also create and use subscribers to handle events in Medusa. + + diff --git a/docs/content/development/events/modules/local.md b/docs/content/development/events/modules/local.md new file mode 100644 index 0000000000..fa900e9df5 --- /dev/null +++ b/docs/content/development/events/modules/local.md @@ -0,0 +1,68 @@ +--- +description: 'In this document, you’ll learn about the local events module and how you can install it in your Medusa backend.' +addHowToData: true +--- + +# Local Events Module + +In this document, you’ll learn about the local events module and how you can install it in your Medusa backend. + +## Overview + +Medusa’s modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides. + +One of these modules is the local events module. This module allows you to utilize Node EventEmitter for the events system in Medusa. The Node EventEmitter is limited to a single process environment. This module is useful for development and testing, but it’s recommended to use the [Redis events module](./redis.md) in production. + +This document will you guide you through installing the local events module. + +--- + +## Prerequisites + +It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx). + +--- + +## Step 1: Install the Module + +In the root directory of your Medusa backend, install the Redis events module with the following command: + +```bash npm2yarn +npm install @medusajs/event-bus-local +``` + +--- + +## Step 2: Add Configuration + +In `medusa-config.js`, add the following to the exported object: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + eventBus: { + resolve: "@medusajs/event-bus-local", + }, + }, +} +``` + +This registers the local events module as the main events service to use. This module does not require any options. + +--- + +## Step 4: Test Module + +To test the module, run the following command to start the Medusa backend: + +```bash npm2yarn +npm run start +``` + +If the module was installed successfully, you should see the following message in the logs: + +```bash noCopy noReport +Local Event Bus installed. This is not recommended for production. +``` diff --git a/docs/content/development/events/modules/redis.md b/docs/content/development/events/modules/redis.md new file mode 100644 index 0000000000..956b2ba765 --- /dev/null +++ b/docs/content/development/events/modules/redis.md @@ -0,0 +1,95 @@ +--- +description: 'In this document, you’ll learn about the Redis events module and how you can install it in your Medusa backend.' +addHowToData: true +--- + +# Redis Events Module + +In this document, you’ll learn about the Redis events module and how you can install it in your Medusa backend. + +## Overview + +Medusa’s modular architecture allows developers to extend or completely replace the logic used for events. You can create a custom module, or you can use the modules Medusa provides. + +One of these modules is the Redis module. This module allows you to utilize Redis for the event bus functionality. When installed, the Medusa’s events system is powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker, and `io-redis` is the underlying Redis client that BullMQ connects to for events storage. + +This document will you guide you through installing the Redis module. + +--- + +## Prerequisites + +### Medusa Backend + +It’s assumed you already have a Medusa backend installed. If not, you can learn how to install it by following [this guide](../../backend/install.mdx). + +### Redis + +You must have Redis installed and configured in your Medusa backend. You can learn how to install Redis in [their documentation](https://redis.io/docs/getting-started/installation/). + +--- + +## Step 1: Install the Module + +In the root directory of your Medusa backend, install the Redis events module with the following command: + +```bash npm2yarn +npm install @medusajs/event-bus-redis +``` + +--- + +## Step 2: Add Environment Variable + +The Redis events module requires a connection URL to Redis as part of its options. If you don’t already have an environment variable set for a Redis URL, make sure to add one: + +```bash +EVENTS_REDIS_URL= +``` + +Where `` is a connection URL to your Redis instance. + +--- + +## Step 3: Add Configuration + +In `medusa-config.js`, add the following to the exported object: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + eventBus: { + resolve: "@medusajs/event-bus-redis", + options: { + redisUrl: process.env.EVENTS_REDIS_URL, + }, + }, + }, +} +``` + +This registers the Redis events module as the main event bus service to use. In the options, you pass `redisUrl` with the value being the environment variable you set. This is the only required option. + +Other available options include: + +- `queueName`: a string indicating the name of the BullMQ queue. By default, it’s `events-queue`. +- `queueOptions`: an object containing options for the BullMQ queue. You can learn about available options in [BullMQ’s documentation](https://api.docs.bullmq.io/interfaces/QueueOptions.html). By default, it’s an empty object. +- `redisOptions`: an object containing options for the Redis instance. You can learn about available options in [io-redis’s documentation](https://luin.github.io/ioredis/index.html#RedisOptions). By default, it’s an empty object. + +--- + +## Step 4: Test Module + +To test the module, run the following command to start the Medusa backend: + +```bash npm2yarn +npm run start +``` + +If the module was installed successfully, you should see the following message in the logs: + +```bash noCopy noReport +Connection to Redis in module 'event-bus-redis' established +``` diff --git a/docs/content/development/events/subscribers.mdx b/docs/content/development/events/subscribers.mdx index 5d8dec00f4..ad74c33e76 100644 --- a/docs/content/development/events/subscribers.mdx +++ b/docs/content/development/events/subscribers.mdx @@ -9,16 +9,6 @@ import Icons from '@theme/Icon'; In this document, you'll learn what Subscribers are in Medusa. -## What are Events - -In Medusa, there are events that are emitted when a certain action occurs. For example, if a customer places an order, the `order.placed` event is emitted with the order data. - -The purpose of these events is to allow other parts of the platform, or third-party integrations, to listen to those events and perform a certain action. That is done by creating subscribers. - -Medusa's queuing and events system is handled by Redis. So, you must have [Redis configured](../backend/prepare-environment.mdx#redis) on your backend to use subscribers. - ---- - ## What are Subscribers Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the `order.placed` event and send the email when the event is emitted. diff --git a/docs/content/development/fundamentals/architecture-overview.md b/docs/content/development/fundamentals/architecture-overview.md index a710d5a400..a717487ead 100644 --- a/docs/content/development/fundamentals/architecture-overview.md +++ b/docs/content/development/fundamentals/architecture-overview.md @@ -18,7 +18,7 @@ The retrieval, manipulation, and other utility methods related to that entity ar The backend does not have any tightly-coupled frontend. Instead, it exposes [**Endpoints**](../endpoints/overview.mdx) which are REST APIs that frontends such as an admin or a storefront can use to communicate with the backend. Endpoints are [Express routes](https://expressjs.com/en/guide/routing.html). -Medusa also uses an [**Events Architecture**](../events/index.md) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/). +Medusa also uses an [**Events Architecture**](../events/index.mdx) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/). Events can be handled using [**Subscribers**](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript classes that add their methods as handlers for specific events. These handler methods are only executed when an event is triggered. diff --git a/docs/content/development/fundamentals/dependency-injection.md b/docs/content/development/fundamentals/dependency-injection.md index b33267448f..fba64501e1 100644 --- a/docs/content/development/fundamentals/dependency-injection.md +++ b/docs/content/development/fundamentals/dependency-injection.md @@ -2,9 +2,9 @@ description: 'Learn what the dependency container is and how to use it in Medusa. Learn also what dependency injection is, and what the resources regsitered and their names are.' --- -# Dependency Injection +# Dependency Container and Injection -In this document, you’ll learn what the dependency injection is and how you can use it in Medusa. +In this document, you’ll learn what the dependency container is and how you can use it in Medusa with dependency injection. ## Introduction @@ -36,13 +36,13 @@ The backend then registers all important resources in the container, which makes The Medusa backend scans the core Medusa package, plugins, and your files in the `dist` directory and registers the following resources: -:::note +:::tip -Many resources are registered under their camel-case name. These resources are formatted by taking the name of the file, transforming it to camel case, then appending the folder name to the name. So, the `services/product.ts` service is registered as `productService`. +The Lifetime column indicates the lifetime of a service. Other resources that aren't services don't have a lifetime, which is indicated with the `-` in the column. You can learn about what a lifetime is in the [Create a Service](../services/create-service.md) documentation. ::: - +
+ @@ -79,6 +84,11 @@ The configurations that are exported from `medusa-config.js`. `configModule` + + @@ -97,6 +107,11 @@ Services that extend the `TransactionBaseService` class. Each service is registered under its camel-case name. For example, the `ProductService` is registered as `productService`. + + @@ -115,6 +130,11 @@ An instance of Typeorm’s Entity Manager. `manager` + + @@ -133,45 +153,60 @@ An instance of Medusa CLI’s logger. You can use it to log messages to the term `logger` + + + + @@ -193,6 +228,11 @@ Every fulfillment provider is registered under two names: - Its camel-case name. For example, the `WebshipperFulfillmentService` is registered as `webshipperFulfillmentService`. - `fp_` followed by its identifier. For example, the `WebshipperFulfillmentService` is registered as `fp_webshipper`. + + @@ -211,6 +251,11 @@ An array of all fulfillment providers that extend the `FulfillmentService` class `fulfillmentProviders` + + @@ -232,6 +277,11 @@ Every notification provider is registered under two names: - Its camel-case name. For example, the `SendGridService` is registered as `sendGridService`. - `noti_` followed by its identifier. For example, the `SendGridService` is registered as `noti_sendgrid`. + + @@ -250,6 +300,11 @@ An array of all notification providers that extend the `AbstractNotificationServ `notificationProviders` + + @@ -271,6 +326,11 @@ The file service is registered under two names: - Its camel-case name. For example, the `MinioService` is registered as `minioService`. - `fileService` + + @@ -292,6 +352,11 @@ The search service is registered under two names: - Its camel-case name. For example, the `AlgoliaService` is registered as `algoliaService`. - `searchService` + + @@ -313,6 +378,11 @@ The tax provider is registered under two names: - Its camel-case name. - `tp_` followed by its identifier. + + @@ -331,6 +401,11 @@ An array of every tax provider that extends the `AbstractTaxService` class. `taxProviders` + + @@ -349,6 +424,11 @@ An instance of every service that extends the `OauthService` class. Each Oauth Service is registered under its camel-case name followed by `Oauth`. + + @@ -367,6 +447,11 @@ An instance of the `FlagRouter`. This can be used to list feature flags, set a f `featureFlagRouter` + + @@ -385,6 +470,11 @@ An instance of the Redis client. If Redis is not configured, a fake Redis client `redisClient` + + @@ -403,6 +493,11 @@ An instance of every entity. Each entity is registered under its camel-case name followed by Model. For example, the `CustomerGroup` entity is stored under `customerGroupModel`. + + @@ -421,6 +516,11 @@ An array of all database entities that is passed to Typeorm when connecting to t `db_entities` + + @@ -439,6 +539,11 @@ An instance of each repository. Each repository is registered under its camel-case name. For example, `CustomerGroupRepository` is stored under `customerGroupRepository`. + + @@ -461,6 +566,11 @@ Each batch job strategy is registered under three names: - `batch_` followed by its identifier. For example, the `ProductImportStrategy` is registered under `batch_product-import-strategy`. - `batchType_` followed by its batch job type. For example, the `ProductImportStrategy` is registered under `batchType_product-import`. + + @@ -479,6 +589,11 @@ An array of all classes extending the `AbstractBatchJobStrategy` abstract class. `batchJobStrategies` + + @@ -497,6 +612,11 @@ An instance of the class implementing the `ITaxCalculationStrategy` interface. `taxCalculationStrategy` + + @@ -515,6 +635,11 @@ An instance of the class extending the `AbstractCartCompletionStrategy` class. `cartCompletionStrategy` + + @@ -533,6 +658,11 @@ An instance of the class implementing the `IPriceSelectionStrategy` interface. `priceSelectionStrategy` + + @@ -551,6 +681,11 @@ An instance of strategies that aren’t of the specific types mentioned above an Its camel-case name. + + @@ -576,7 +711,7 @@ Please note that in endpoints some resources, such as repositories, are not avai ### In Classes -In classes such as services, strategies, or subscribers, you can load resources in the constructor function. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class. +In classes such as services, strategies, or subscribers, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class. For example: diff --git a/docs/content/development/modules/create.mdx b/docs/content/development/modules/create.mdx new file mode 100644 index 0000000000..296743c8e5 --- /dev/null +++ b/docs/content/development/modules/create.mdx @@ -0,0 +1,305 @@ +--- +description: 'Learn how to create a module and test the module in your Medusa backend.' +--- + +import DocCardList from '@theme/DocCardList'; +import Icons from '@theme/Icon'; + +# How to Create a Module + +In this document, you’ll learn how to create a module and test the module in your Medusa backend. + +This document covers the general steps of creating an internal module, but does not explore actually implementing any functionality in it. An internal module is a TypeScript/JavaScript module that is loaded by the Medusa backend as part of the commerce application to extend or replace a functionality within it, such as the cache or event bus functionality. + +--- + +## (Optional) Step 0: Project Preparation + +Before you start implementing the custom functionality in your module, it's recommended to create a directory that holds your module and prepare the following structure in it: + +``` +custom-module +| +|___ index.ts +| +|___ services // directory +``` + +The directory can be an NPM project, but that is optional. `index.ts` acts as an entry point to your Module. You'll learn about its content in a later step. The `service` directory will hold your custom services. If you're adding other resources you can add other directories for them. For example, if you're adding an entity you can add a `models` directory. + +:::tip + +You can use JavaScript instead of TypeScript. + +::: + +It's also recommended to use the following TypeScript config in `tsconfig.json`, which should be added in the root of the project holding your module: + +```json +{ + "compilerOptions": { + "lib": [ + "es2020" + ], + "target": "2020", + "outDir": "./dist", + "esModuleInterop": true, + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "allowJs": true, + "skipLibCheck": true, + }, + "include": ["src"], + "exclude": [ + "dist", + "./src/**/__tests__", + "./src/**/__mocks__", + "./src/**/__fixtures__", + "node_modules" + ] +} +``` + +--- + +## Step 1: Implement the Custom Functionality + +This step depends on what you’re actually implementing. For example, you can implement the cache or events module, or you can implement a commerce module. If what you’re creating has a guide, you can refer to it while implementing the functionalities. + +### Note About Project Structure + +When developing your module, it's important to note that you'll later be referencing the module using a file path to an `index.ts` or `index.js` file. This file is explained in the next step and acts as an entry point and a definition of your module. + +So, make sure when implementing your module you take into account that the module should be easily referenced from your local Medusa server. For example, you can develop your module in a sibling directory of the Medusa backend that you can reference with `../custom-module`. + +Keep in mind that when you publish the module to NPM, you'll have to move your module into a new NPM project. This is covered in the [Publish Module documentation](./publish.md). + +### Recommended Guides + + + +--- + +### Step 2: Export Module + +After implementing the module, you must export a module object that helps the Medusa backend understand how to use this Module. This is done in the file `index.ts`. + +The file must export an object with the following properties: + +```ts +type ModuleExports = { + service: Constructor + loaders?: ModuleLoaderFunction[] + migrations?: any[] + models?: Constructor[] + runMigrations?( + options: LoaderOptions, + moduleDeclaration: InternalModuleDeclaration + ): Promise + revertMigration?( + options: LoaderOptions, + moduleDeclaration: InternalModuleDeclaration + ): Promise +} +``` + +:::tip + +All property types such as `ModuleLoaderFunction` can be loaded from the `@medusajs/modules-sdk` package. + +::: + +Where: + +- `service`: This is the only required property to be exported. It should be the main service your module exposes, and it must implement all the declared methods on the module interface. For example, if it's a cache module, it must implement the `ICacheService` interface exported from `@medusajs/types`. +- `loaders`: (optional) an array of functions used to perform an action while loading the module. For example, you can log a message that the module has been loaded, or if your module's scope is [isolated](#module-scope) you can use the loader to establish a database connection. +- `migrations`: (optional) an array of objects containing database migrations that should run when the `migration` command is used with Medusa's CLI. +- `models`: (optional) an array of entities that your module creates. +- `runMigrations`: (optional) a function that can be used to define migrations to run when the `migration run` command is used with Medusa's CLI. The migrations will only run if they haven't already. This will only be executed if the module's scope is [isolated](#module-scope). +- `revertMigration`: (optional) a function can be used to define how migrations should be reverted when the `migration revert` command is used with Medusa's CLI. This will only be executed if the module's scope is [isolated](#module-scope). + +Here's an example implementation of `index.ts` from Medusa's Redis Cache module: + +```ts title=index.ts +import { ModuleExports } from "@medusajs/modules-sdk" + +import Loader from "./loaders" +import { RedisCacheService } from "./services" + +const service = RedisCacheService +const loaders = [Loader] + +const moduleDefinition: ModuleExports = { + service, + loaders, +} + +export default moduleDefinition +``` + +--- + +## Step 3: Reference Module + +To use your module in the Medusa backend, add your module to `medusa-config.js`: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "module-name-or-path", + options: { + // options if necessary + }, + // optional + resources: "shared", + }, + }, +} +``` + +The way you add your module depends on its type and what options it requires, if any. Note that in the above code example: + +- `moduleType` is the type of your module. For example, if your module is a cache module, it should be changed to `cacheService`. +- `resolve` is used to reference the Module. Its value should be either the name of an NPM package module, or a relative file path to the module. This is explained more in the [Module Reference section](#module-reference). +- `options` should hold any options of your module, if necessary. +- `resources` is an optional property that indicates whether the module shares the same dependency container as the rest of the resources in the Medusa backend. More details are explained in the [Module Scope section](#module-scope). + +### Module Reference + +When the module is installed as an NPM package, the value of the `resolve` property should be the name of that package. For example: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "custom-module", + // ... + }, + }, +} +``` + +However, when using a local module, you must reference the module using a relative file path to it from the Medusa backend. + +For example, consider you have the following file structure: + +``` +| +|___ custom-module +| | +| |___ index.ts +| | +| |___ services +| | | +| | |___ custom-service.ts +| |___ // more files +| +| +|___ medusa-backend +``` + +You can reference your module in two ways: + +1\. Referencing the directory: In this case, it's assumed that the `index.ts` file that contains the module definition is in the root of the directory you referenced. Using the above example, the file path would be in this case: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "../custom-module", + // ... + }, + }, +} +``` + +2\. Referencing `index` file: In this case, it's assumed that the `index.ts` or `index.js` file you're referencing includes the module definition. Using the above example, the file path would be in this case: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "../custom-module/index.ts", + // ... + }, + }, +} +``` + +### Module Scope + +By default, the module shares the same dependency container used across the Medusa backend. So, the module can benefit from the core services and other resources available through [dependency injection](../fundamentals/dependency-injection.md). The module can also benefit from the same database connection. + +The module's scope can be changed using the `resources` property available as part of the module's configurations: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + // other configurations + resources: "shared", + }, + }, +} +``` + +The `resources` property can have one of the following values: + +- `shared`: (default) The dependency container is shared with the module, including the database connection. You don't need to establish the database connection yourself in a loader. +- `isolated`: the module receives an empty dependency container, and only its own dependencies will be registered in the container. When using this value, you must establish the database connection yourself and managing other resources within your module. + +--- + +## Step 4: Test Your Module + +Finally, to test your module, run the following command: + +```bash npm2yarn +npm run start +``` + +This starts the Medusa backend and runs your module as part of it. + +--- + +## Next Steps + +After you finish developing your module, you can publish it as an NPM package with [this guide](./publish.md). diff --git a/docs/content/development/modules/overview.mdx b/docs/content/development/modules/overview.mdx new file mode 100644 index 0000000000..50b418752a --- /dev/null +++ b/docs/content/development/modules/overview.mdx @@ -0,0 +1,47 @@ +--- +description: 'Learn what Modules are and how can you use them during your custom development with Medusa.' +--- + +import DocCardList from '@theme/DocCardList'; +import Icons from '@theme/Icon'; + +# Modules + +In this document, you’ll learn what Modules are and how can you use them during your custom development with Medusa. + +## Overview + +Modules are self-contained, reusable pieces of code that encapsulate specific functionality or features within an ecommerce application. They foster separation of concerns, maintainability, and reusability by organizing code into smaller, independent units that can be easily managed, tested, and integrated with other modules. + +Modules further increase Medusa’s extensibility. Commerce modules, such as the cart engine, can be extended or entirely replaced with your own custom logic. They can also run independently of the core Medusa package, allowing you to utilize the commerce module within a larger commerce ecosystem. For example, you can use the Order module as an Order Management System (OMS) without using Medusa’s core. + +This also applies to core logic such as caching or events systems. You can use modules to integrate any logic or third-party service to handle this logic. This gives you greater flexibility in how you choose your tech stack. + +Modules are created and loaded similarly to plugins. They can be loaded from a local project, or they can be installed and loaded from an NPM package. In the Medusa backend, they’re added as part of the configurations in `medusa-config.js` to use and load them within the backend. + +--- + +## Custom Development + +Developers can create their own modules and use them in their Medusa backend. They can also publish these modules to NPM to reuse them across Medusa backend or allow other developers to use them. + + diff --git a/docs/content/development/modules/publish.md b/docs/content/development/modules/publish.md new file mode 100644 index 0000000000..f3e615777a --- /dev/null +++ b/docs/content/development/modules/publish.md @@ -0,0 +1,198 @@ +--- +description: 'Learn how to prepare and publish your custom module to NPM, then how to install it in the Medusa backend.' +--- + +# How to Publish a Module + +In this document, you'll learn how to prepare and publish your custom module to NPM, then how to install it in the Medusa backend. + +## Prerequisites + +This guide assumes you've already created a custom module. If not, follow [this guide](./create.mdx) first to create a module. + +You also need an [NPM account](https://www.npmjs.com/signup) to publish the module with. + +--- + +## Step 1: Create an NPM Project + +If your module isn't located in an NPM project already, you must create one first that will hold your module. + +To do that, run the following commands to create a directory and initialize an NPM project in it: + +```bash npm2yarn +mkdir my-module +npm init +``` + +You’ll be asked a couple of questions related to your package, such as its name or license. You can keep the default for now or set them right away. + +Once you’re done, you should have a `package.json` created in the directory. + +--- + +## Step 2: Changes to package.json + +In your `package.json` file, add or update the following fields: + +```json title=package.json +{ + // other fields + "main": "dist/index.js", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@medusajs/types": "^0.0.2", + "cross-env": "^5.2.1", + "typescript": "^4.4.4" + }, + "scripts": { + "watch": "tsc --build --watch", + "prepare": "cross-env NODE_ENV=production npm run build", + "build": "tsc --build", + }, + "dependencies": { + "@medusajs/modules-sdk": "^0.1.0", + } +} +``` + +This adds the necessary dependencies for development and publishing, including the `@medusajs/modules-sdk` package. It also adds the following scripts: + +- `build`: can be used to manually build your module. +- `prepare`: can be used to prepare your module for publishing on NPM +- `watch`: (optional, for development) can be used to re-build your module whenever any changes occur without having to manually trigger the `build`. + +--- + +## Step 3: Configure tsconfig.json + +If you don't already have a `tsconfig.json` file, create one in the root of your NPM project with the following content: + +```json title=tsconfig.json +{ + "compilerOptions": { + "lib": [ + "es2020" + ], + "target": "2020", + "outDir": "./dist", + "esModuleInterop": true, + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "allowJs": true, + "skipLibCheck": true, + }, + "include": ["src"], + "exclude": [ + "dist", + "./src/**/__tests__", + "./src/**/__mocks__", + "./src/**/__fixtures__", + "node_modules" + ] +} +``` + +This allows you to use the recommended TypeScript configurations and sets the output directory to `dist`. This is essential for preparing your module for publishing. + +--- + +## Step 4: Change Module Structure + +To ensure that the files are built from the `src` directory to the `dist` directory, make sure to move the module content to a `src` directory inside the new NPM project. + +--- + +## Step 5: Publish and Use Module + +This section explains how to publish your module to NPM. + +### Run Prepare Command + +Before you publish or update your module, make sure to run the `prepare` command defined earlier: + +```bash npm2yarn +npm run prepare +``` + +### Login + +In your terminal, log in with your NPM account: + +```bash +npm login +``` + +You’ll be asked to enter your NPM email and password. + +### Publish Module Package + +Once you’re logged in, you can publish your package with the following command: + +```bash +npm publish +``` + +Your package is then published on NPM and everyone can use it and install it. + +### Install Module + +To install your published module, you can run the following command on any Medusa backend project: + +```bash +npm install module-name +``` + +Where `module-name` is the name of your module. + +### Add Module to medusa-config.js + +In `medusa-config.js` on your Medusa backend, add your module to the exported configurations: + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + // ... + moduleType: { + resolve: "", + options: { + // options if necessary + }, + }, + }, +} +``` + +Where `` is the name of your NPM package. + +You can learn more about the available options in the [Create Module documentation](./create.mdx#step-3-reference-module). + +### Update Module + +To update your module at a later point, you can run the following command to change the NPM version: + +```bash +npm version +``` + +Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. You can see the [full list of types in NPM’s documentation](https://docs.npmjs.com/cli/v8/commands/npm-version). + +Then, publish the new update: + +```bash +npm publish +``` diff --git a/docs/content/development/notification/create-notification-provider.md b/docs/content/development/notification/create-notification-provider.md index c072f30c47..387f210eee 100644 --- a/docs/content/development/notification/create-notification-provider.md +++ b/docs/content/development/notification/create-notification-provider.md @@ -15,9 +15,7 @@ If you’re unfamiliar with the Notification architecture in Medusa, it is recom ## Prerequisites -Before you start creating a Notification Provider, you need to either install a [Medusa backend](../backend/install.mdx), or create it in a [plugin](../plugins/overview.mdx). - -You also need to [setup Redis](../backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../backend/configurations.md#redis) to test out the Notification provider. +Before you start creating a Notification Provider, you need to either install a [Medusa backend](../backend/install.mdx), or create it in a [plugin](../plugins/overview.mdx). The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. --- @@ -297,7 +295,7 @@ Notice that the value of the `identifier` static property defined in the `EmailS ## Test Sending Notifications with your Notification Provider -Make sure you've configured Redis with your Medusa backend as explained in the [Prerequisites](#prerequisites) section and that the Redis service is running. +Make sure you have an event bus module configured in your Medusa backend. You can learn more on how to do that in the [Configurations guide](../backend/configurations.md). Then, start by running your Medusa backend: diff --git a/docs/content/development/plugins/overview.mdx b/docs/content/development/plugins/overview.mdx index 04661efaab..126596a6df 100644 --- a/docs/content/development/plugins/overview.mdx +++ b/docs/content/development/plugins/overview.mdx @@ -15,7 +15,7 @@ Medusa was built with flexibility and extendibility in mind. All different compo Developers can use plugins to take advantage of this abstraction, flexibility, and extendibility. Plugins allow developers to implement custom features or integrate third-party services into Medusa. -For example, if you want to use Stripe as a payment provider in your store, then you can install the Stripe plugin on your backend and use it. +For example, if you want to use Stripe as a payment processor in your store, then you can install the Stripe plugin on your backend and use it. An alternative approach is developing a custom way of handling payment on your ecommerce store. Both approaches are achievable by either creating a plugin or using an existing plugin. diff --git a/docs/content/development/scheduled-jobs/create.md b/docs/content/development/scheduled-jobs/create.md index 7a70e73839..3f30c92584 100644 --- a/docs/content/development/scheduled-jobs/create.md +++ b/docs/content/development/scheduled-jobs/create.md @@ -19,11 +19,7 @@ This guide explains how to create a scheduled job on your Medusa backend. The sc ### Medusa Components -It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. - -### Redis - -Redis is required for scheduled jobs to work. Make sure you [install Redis](../../development/backend/prepare-environment.mdx#redis) and [configure it with your Medusa backend](../../development/backend/configurations.md#redis). +It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. --- diff --git a/docs/content/development/scheduled-jobs/overview.mdx b/docs/content/development/scheduled-jobs/overview.mdx index d9ee10fb40..14aa4b8b58 100644 --- a/docs/content/development/scheduled-jobs/overview.mdx +++ b/docs/content/development/scheduled-jobs/overview.mdx @@ -15,7 +15,7 @@ Scheduled jobs (also known as cron jobs) are tasks performed at a specific time For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day using a scheduled job. -In the Medusa Backend, the scheduled jobs queue is implemented using [Redis](https://redis.io/) and [Bull](https://www.npmjs.com/package/bull). So, for scheduled jobs to work, you must have [Redis enabled](../../development/backend/configurations.md#redis). +In the Medusa Backend, the scheduled jobs queue is implemented using [Redis](https://redis.io/) and [Bull](https://www.npmjs.com/package/bull). So, for scheduled jobs to work, you must have [Redis installed and enabled](../../development/backend/configurations.md#redis). :::tip diff --git a/docs/content/development/services/create-service.md b/docs/content/development/services/create-service.md index 3c5450e5d3..e4d8e39dae 100644 --- a/docs/content/development/services/create-service.md +++ b/docs/content/development/services/create-service.md @@ -3,13 +3,13 @@ description: 'Learn how to create a service in Medusa. This guide also includes addHowToData: true --- -# Create a Service +# How to Create a Service In this document, you’ll learn how you can create a [Service](./overview.mdx) and use it across your Medusa backend just like any of the core services. -## Implementation +## Service Implementation -To create a service, create a TypeScript or JavaScript file in `src/services` to hold the service. The name of the file should be the registration name of the service without `Service` as it will be appended to it by default. +To create a service, create a TypeScript or JavaScript file in `src/services` to hold the service. The name of the file should be the name of the service without `Service`. This is essential as the file name is used when registering the service in the [dependency container](../fundamentals/dependency-injection.md), and `Service` is appended to the camel-case version of the file name automatically. For example, if you want to create a service `helloService`, create the file `hello.ts` in `src/services` with the following content: @@ -28,6 +28,33 @@ class HelloService extends TransactionBaseService { export default HelloService ``` +This service will be registered in the dependency container as `helloService`. + +--- + +## Service Life Time + +As the dependency container in Medusa is built on top of [awilix](https://github.com/jeffijoe/awilix), you can specify the [Lifetime](https://github.com/jeffijoe/awilix#lifetime-management) of a service. The lifetime is added as a static property to the service. + +There are three lifetime types: + +1. `Lifetime.TRANSIENT`: (default for custom services) when used, a new instance of the service is created everytime it is resolved in other resources from the dependency container. +2. `Lifetime.SCOPED`: when used, an instance of the service is created and reused in the scope of the dependency container. So, when the service is resolved in other resources that share that dependency container, the same instance of the service will be returned. +3. `Lifetime.SINGLETON`: (default for core services) when used, the service is always reused, regardless of the scope. An instance of the service is cached in the root container. + +You can set the lifetime of your service by setting the `LIFE_TIME` static property: + +```ts title=/src/services/hello.ts +import { TransactionBaseService } from "@medusajs/medusa" +import { Lifetime } from "awilix" + +class HelloService extends TransactionBaseService { + static LIFE_TIME = Lifetime.SCOPED + + // ... +} +``` + --- ## Service Constructor diff --git a/docs/content/development/services/extend-service.md b/docs/content/development/services/extend-service.md new file mode 100644 index 0000000000..a40a176771 --- /dev/null +++ b/docs/content/development/services/extend-service.md @@ -0,0 +1,86 @@ +--- +description: 'Learn how to create a service in Medusa. This guide also includes how to use services in other services, subscribers, and endpoints.' +addHowToData: true +--- + +# How to Extend a Service + +In this document, you’ll learn how to extend a core service in Medusa. + +## Overview + +Medusa’s core services cover a wide range of functionalities related to each domain or entity. You can extend these services to add custom methods or override existing methods. + +### Word of Caution about Overriding + +Extending services to add new methods shouldn't cause any issues within your commerce application. However, if you extend them to override their existing methods, you should be aware that this could have negative implications, such as unanticipated bugs, especially when you try to upgrade the core Medusa package to a newer version. + +--- + +## Step 1: Create the Service File + +In your Medusa backend, create the file `src/services/product.ts`. This file will hold your extended service. + +Note that the name of the file must be the same as the name of the original service in the core package. So, if you’re extending the `ProductService`, the file’s name should be `product.ts`. On the other hand, if you’re extending the `CustomerService`, the file’s name should be `customer.ts`. + +--- + +## Step 2: Implementing the Service + +In the file, you can import the original service from the Medusa core, then create your service that extends the core service. + +For example, to extend the Product service: + +```ts title=src/services/product.ts +import { + ProductService as MedusaProductService, +} from "@medusajs/medusa" + +class ProductService extends MedusaProductService { + // TODO add customizations +} + +export default ProductService +``` + +Notice that you alias the `ProductService` of the core to avoid naming conflicts. + +Within the service, you can add new methods or extend existing ones. + +You can also change the lifetime of the service: + +```ts title=src/services/product.ts +import { Lifetime } from "awilix" +import { + ProductService as MedusaProductService, +} from "@medusajs/medusa" + +class ProductService extends MedusaProductService { + // The default life time for a core service is SINGLETON + static LIFE_TIME = LifeTime.SCOPED + + // ... +} + +export default ProductService +``` + +You can learn more details about the service lifetime and other considerations when creating a service in the [Create Service documentation](./create-service.md). + +--- + +## Step 3: Test it Out + +To test out your customization, start by transpiling your files by running the following command in the root directory of the Medusa backend: + +```bash npm2yarn +npm run build +``` + +Then, start the backend: + +```bash npm2yarn +npm run start +``` + +You should see the customizations you made in effect. diff --git a/docs/content/development/services/overview.mdx b/docs/content/development/services/overview.mdx index a066fccdd3..1799b6de05 100644 --- a/docs/content/development/services/overview.mdx +++ b/docs/content/development/services/overview.mdx @@ -2,7 +2,7 @@ description: 'Learn what Services are in Medusa. Services represent bundled helper methods that you want to use across your commerce application.' --- -import DocCard from '@theme/DocCard'; +import DocCardList from '@theme/DocCardList'; import Icons from '@theme/Icon'; # Services @@ -17,11 +17,15 @@ For example, you can use Medusa’s `productService` to get the list of products In the Medusa backend, custom services are TypeScript or JavaScript files located in the `src/services` directory. Each service should be a class that extends the `TransactionBaseService` class from the core Medusa package `@medusajs/medusa`. Each file you create in `src/services` should hold one service and export it. -The file name is important as it determines the name of the service when you need to use it elsewhere. The name of the service will be registered as the camel-case version of the file name + `Service` at the end of the name. +The file name is important as it determines the name of the service when you need to use it elsewhere. The name of the service will be registered in the dependency container as the camel-case version of the file name with `Service` appended to the end of the name. Other resources, such as other services or endpoints, will use that name when resolving the service from the dependency container. -For example, if the file name is `hello.ts`, the service will be registered as `helloService`. If the file name is `hello-world.ts`, the service name will be registered as `helloWorldService`. +For example, if the file name is `hello.ts`, the service will be registered as `helloService` in the dependency container. If the file name is `hello-world.ts`, the service name will be registered as `helloWorldService`. -The registration name of the service is important, as you’ll be referring to it when you want to get access to the service using dependency injection or in routes. +:::note + +You can learn more about the dependency container and how it works in the [dependency injection](../fundamentals/dependency-injection.md) documentation. + +::: The service must then be transpiled using the `build` command, which moves them to the `dist` directory, to be used across your commerce application. @@ -37,7 +41,8 @@ If you're creating a service in a plugin, learn more about the required structur Developers can create custom services in the Medusa backend, a plugin, or in a Commerce Module. - \ No newline at end of file + }, + { + type: 'link', + href: '/development/services/extend-service', + label: 'Extend a Service', + customProps: { + icon: Icons['academic-cap-solid'], + description: 'Learn how to extend a core Medusa service.' + } + }, +]} /> diff --git a/docs/content/homepage.mdx b/docs/content/homepage.mdx index a77b8a6d9f..401031b488 100644 --- a/docs/content/homepage.mdx +++ b/docs/content/homepage.mdx @@ -75,10 +75,10 @@ Medusa provides the essential building blocks that developers can put together t { type: 'link', href: '/admin/quickstart', - label: 'Medusa Admin Quickstart', + label: 'Admin Dashboard', customProps: { icon: Icons['computer-desktop-solid'], - description: 'The Medusa admin is an intuitive admin dashboard that can be used along with the Medusa backend and modules. Merchants can use it to perform data management and processes such as manage orders, products, customers, and much more.' + description: 'An intuitive admin dashboard along with the Medusa backend and commerce modules. Merchants can use it to perform data management and processes such as manage orders, products, customers, and much more.' } }, ]} /> @@ -162,29 +162,41 @@ Learn about all the new features and enhancements in Medusa. diff --git a/docs/content/medusa-react/overview.md b/docs/content/medusa-react/overview.md index 44a7229013..b4c4e13973 100644 --- a/docs/content/medusa-react/overview.md +++ b/docs/content/medusa-react/overview.md @@ -396,7 +396,7 @@ The `useCart` hook returns an object with the following properties: - `createCart`: A mutation used to create a cart. - `updateCart`: A mutation used to update a cart’s details such as region, customer email, shipping address, and more. - `startCheckout`: A mutation used to initialize payment sessions during checkout. -- `pay`: A mutation used to select a payment provider during checkout. +- `pay`: A mutation used to select a payment processor during checkout. - `addShippingMethod`: A mutation used to add a shipping method to the cart during checkout. - `completeCheckout`: A mutation used to complete the cart and place the order. diff --git a/docs/content/modules/carts-and-checkout/backend/add-payment-provider.md b/docs/content/modules/carts-and-checkout/backend/add-payment-provider.md index f129f59afc..e0db0179a5 100644 --- a/docs/content/modules/carts-and-checkout/backend/add-payment-provider.md +++ b/docs/content/modules/carts-and-checkout/backend/add-payment-provider.md @@ -1,158 +1,167 @@ --- -description: 'Learn how to create a payment provider in the Medusa backend. This guide explains the different methods available in a fulfillment provider.' +description: 'Learn how to create a payment processor in the Medusa. This guide explains the different methods available in a payment processor.' addHowToData: true --- -# How to Create a Payment Provider +# How to Create a Payment Processor -In this document, you’ll learn how to add a Payment Provider to your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.md) first. - -## Overview - -A Payment Provider is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Provider is Stripe. - -By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa. - -Adding a Payment Provider is as simple as creating a [service](../../../development/services/create-service.md) file in `src/services`. A Payment Provider is essentially a service that extends `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`. - -Payment Provider Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Provider in the Medusa backend. - -:::tip - -Payment Providers are loaded and installed at the backend startup. - -::: - -The Payment Provider service is also required to implement the following methods: - -1. `createPayment`: Called when a Payment Session for the Payment Provider is to be created. -2. `retrievePayment`: Used to retrieve payment data from the third-party provider, if there’s any. -3. `getStatus`: Used to get the status of a Payment or Payment Session. -4. `updatePayment`: Used to update the Payment Session whenever the cart and its related data are updated. -5. `updatePaymentData`: Used to update the `data` field of Payment Sessions. Specifically called when a request is sent to the [Update Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) endpoint. -6. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted. -7. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created. -8. `getPaymentData`: Used to retrieve the data that should be stored in the `data` field of a new Payment instance after the payment amount has been authorized. -9. `capturePayment`: Used to capture the payment amount of an order or swap. -10. `refundPayment`: Used to refund a payment amount of an order or swap. -11. `cancelPayment`: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled. +In this document, you’ll learn how to create a Payment Processor in your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.md) first. :::note -All these methods must be declared async in the Payment Provider Service. +Before v1.8 of Medusa, this guide explained how to create a payment provider. Payment Providers are now considered legacy and are deprecated. Moving forward, it's recommended to create a Payment Processor that implements the Payment Processor API. + +::: + +## Overview + +A Payment Processor is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Processor is Stripe. + +By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa. + +Adding a Payment Processor is as simple as creating a [service](../../../development/services/create-service.md) file in `src/services`. A Payment Processor is essentially a service that extends `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`. + +Payment Processor Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Processor in the Medusa backend. + +:::tip + +Payment Processors are loaded and installed at the server startup. If not already saved, they're saved in the database and are represented by the `PaymentProvider` entity. + +::: + +The Payment Processor is also required to implement the following methods: + +1. `initiatePayment`: Called when a Payment Session for the Payment Provider is to be created. +2. `retrievePayment`: Used to retrieve payment session data, which can be retrieved from a third-party provider. +3. `getPaymentStatus`: Used to get the status of a Payment or Payment Session. +4. `updatePayment`: Used to update the Payment Session whenever the cart and its related data are updated. +5. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted. For example, you can cancel the payment with the third-party provider. +6. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created. +7. `capturePayment`: Used to capture the payment amount of an order or swap. +8. `refundPayment`: Used to refund a payment amount of an order or swap. +9. `cancelPayment`: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled. + +:::note + +All these methods must be declared async in the Payment Processor. ::: These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed. -![Checkout Flow - Payment](https://res.cloudinary.com/dza7lstvk/image/upload/v1677770739/Medusa%20Docs/Diagrams/checkout-payment_rouet2.png) +![Checkout Flow - Payment](https://res.cloudinary.com/dza7lstvk/image/upload/v1680177820/Medusa%20Docs/Diagrams/checkout-payment_cy9efp.jpg) --- -## Create a Payment Provider +## Create a Payment Processor -The first step to create a payment provider is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment provider. +The first step to create a payment processor is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment processor, and it should be in snake case. -For example, create the file `src/services/my-payment.ts` with the following content: +For example, create the file `src/services/my-payment-processor.ts` with the following content: -```ts title=src/services/my-payment.ts +```ts title=src/services/my-payment-processor.ts import { - AbstractPaymentService, PaymentContext, Data, - Payment, PaymentSession, PaymentSessionStatus, - PaymentSessionData, Cart, PaymentData, - PaymentSessionResponse } from "@medusajs/medusa" + AbstractPaymentProcessor, + PaymentProcessorContext, + PaymentProcessorError, + PaymentProcessorSessionResponse, + PaymentSessionStatus, +} from "@medusajs/medusa" -import { EntityManager } from "typeorm" +class MyPaymentProcessor extends AbstractPaymentProcessor { + static identifier = "my-payment" -class MyPaymentService extends AbstractPaymentService { - protected manager_: EntityManager - protected transactionManager_: EntityManager | undefined - - async getPaymentData( - paymentSession: PaymentSession - ): Promise { - throw new Error("Method not implemented.") - } - async updatePaymentData( - paymentSessionData: PaymentSessionData, - data: Data - ): Promise { - throw new Error("Method not implemented.") - } - async createPayment( - context: Cart & PaymentContext - ): Promise { - throw new Error("Method not implemented.") - } - async retrievePayment(paymentData: Data): Promise { - throw new Error("Method not implemented.") - } - async updatePayment( - paymentSessionData: PaymentSessionData, - cart: Cart - ): Promise { + async capturePayment( + paymentSessionData: Record + ): Promise | PaymentProcessorError> { throw new Error("Method not implemented.") } async authorizePayment( - paymentSession: PaymentSession, - context: Data - ): Promise<{ - data: PaymentSessionData; - status: PaymentSessionStatus - }> { + paymentSessionData: Record, + context: Record + ): Promise< + PaymentProcessorError | + { + status: PaymentSessionStatus; + data: Record; + } + > { throw new Error("Method not implemented.") } - async capturePayment(payment: Payment): Promise { + async cancelPayment( + paymentSessionData: Record + ): Promise | PaymentProcessorError> { throw new Error("Method not implemented.") } - async refundPayment( - payment: Payment, - refundAmount: number - ): Promise { - throw new Error("Method not implemented.") - } - async cancelPayment(payment: Payment): Promise { + async initiatePayment( + context: PaymentProcessorContext + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse + > { throw new Error("Method not implemented.") } async deletePayment( - paymentSession: PaymentSession - ): Promise { + paymentSessionData: Record + ): Promise | PaymentProcessorError> { throw new Error("Method not implemented.") } - async getStatus(data: Data): Promise { + async getPaymentStatus( + paymentSessionData: Record + ): Promise { + throw new Error("Method not implemented.") + } + async refundPayment( + paymentSessionData: Record, + refundAmount: number + ): Promise | PaymentProcessorError> { + throw new Error("Method not implemented.") + } + async retrievePayment( + paymentSessionData: Record + ): Promise | PaymentProcessorError> { + throw new Error("Method not implemented.") + } + async updatePayment( + context: PaymentProcessorContext + ): Promise< + void | + PaymentProcessorError | + PaymentProcessorSessionResponse + > { throw new Error("Method not implemented.") } - } -export default MyPaymentService +export default MyPaymentProcessor ``` -Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`. +Where `MyPaymentProcessor` is the name of your Payment Processor service. -Payment Providers must extend `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`. +Payment Processors must extend `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`. :::tip -Following the naming convention of Services, the name of the file should be the slug name of the Payment Provider, and the name of the class should be the camel case name of the Payment Provider suffixed with “Service”. In the example above, the name of the file should be `my-payment.js`. You can learn more in the [service documentation](../../../development/services/create-service.md). +Following the naming convention of Services, the name of the file should be the slug name of the Payment Processor, and the name of the class should be the camel case name of the Payment Processors suffixed with “Service”. In the example above, the name of the file should be `my-payment.ts`. You can learn more in the [service documentation](../../../development/services/create-service.md). ::: -### Identifier +### identifier -As mentioned in the overview, Payment Providers should have a static `identifier` property. +As mentioned in the overview, Payment Processors should have a static `identifier` property. -The `PaymentProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Provider is created in the database. +The `PaymentProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Processor is created in the database. -The value of this property will also be used to reference the Payment Provider throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). +The value of this property will also be used to reference the Payment Processor throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). + +The identifier can be retrieved using the `getIdentifier` method, which is defined in `AbstractPaymentProcessor`. ### constructor -You can use the `constructor` of your Payment Provider to have access to different services in Medusa through dependency injection. +You can use the `constructor` of your Payment Processor to have access to different services in Medusa through [dependency injection](../../../development/fundamentals/dependency-injection.md). You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service. -Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter: +Additionally, if you’re creating your Payment Processor as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter: ```ts class MyPaymentService extends AbstractPaymentService { @@ -165,42 +174,86 @@ class MyPaymentService extends AbstractPaymentService { } ``` -### createPayment +### PaymentProcessorError -This method is called during checkout when [Payment Sessions are initialized](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) to present payment options to the customer. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment. - -For example, in Stripe this method is used to initialize a Payment Intent for the customer. - -The method receives a context object as a first parameter. This object is of type `PaymentContext` and has the following properties: +Before diving into the methods you'll need to implement, you'll notice that part of the expected return signature of these method includes `PaymentProcessorError`. This is an interface of the following definition: ```ts -type PaymentContext = { - cart: { - context: Record - id: string - email: string - shipping_address: Address | null - shipping_methods: ShippingMethod[] - } - currency_code: string - amount: number - resource_id?: string - customer?: Customer +interface PaymentProcessorError { + error: string + code?: string + detail?: any } ``` -:::note +While implementing the following methods, if you need to inform the Medusa core that an error occurred at a certain stage, return an object having the attributes defined in the `PaymentProcessorError` interface. -Before v1.7.2, the first parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`. - -::: - -This method must return an object of type `PaymentSessionResponse`. It should have the following properties: +For example, the Stripe payment processor has the following method to create the error object, which is used within other methods: ```ts -type PaymentSessionResponse = { - update_requests: { - customer_metadata: Record +abstract class StripeBase extends AbstractPaymentProcessor { + // ... + protected buildError( + message: string, + e: Stripe.StripeRawError | PaymentProcessorError | Error + ): PaymentProcessorError { + return { + error: message, + code: "code" in e ? e.code : "", + detail: isPaymentProcessorError(e) + ? `${e.error}${EOL}${e.detail ?? ""}` + : "detail" in e + ? e.detail + : e.message ?? "", + } + } + + // used in other methods + async retrievePayment( + paymentSessionData: Record + ): Promise< + PaymentProcessorError | + PaymentProcessorSessionResponse["session_data"] + > { + try { + // ... + } catch (e) { + return this.buildError( + "An error occurred in retrievePayment", + e + ) + } + } +} +``` + +### initiatePayment + +This method is called either if a region has only one payment provider enabled or when [a Payment Session is selected](/api/store#tag/Cart/operation/PostCartsCartPaymentSession), which occurs when the customer selects their preferred payment method during checkout. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment. + +For example, in Stripe this method is used to create a Payment Intent for the customer. + +The method receives a context object as a first parameter. This object is of type `PaymentProcessorContext` and has the following properties: + +```ts +type PaymentProcessorContext = { + billing_address?: Address | null + email: string + currency_code: string + amount: number + resource_id: string + customer?: Customer + context: Record + paymentSessionData: Record +} +``` + +This method must return an object of type `PaymentProcessorSessionResponse`. It should have the following properties: + +```ts +type PaymentProcessorSessionResponse = { + update_requests?: { + customer_metadata?: Record } session_data: Record } @@ -209,21 +262,23 @@ type PaymentSessionResponse = { Where: - `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.md), the `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point. -- `update_requests` is an object that can be used to pass data from the payment provider plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer. +- `update_requests` is an object that can be used to pass data from the Payment Processor plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer. -An example of a minimal implementation of `createPayment`: +An example of a minimal implementation of `initiatePayment`: ```ts -import { - PaymentContext, +import { + PaymentContext, PaymentSessionResponse, } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... - async createPayment( - context: Cart & PaymentContext - ): Promise { + async initiatePayment( + context: PaymentProcessorContext + ): Promise< + PaymentProcessorError | PaymentProcessorSessionResponse + > { // prepare data return { session_data, @@ -235,9 +290,9 @@ class MyPaymentService extends AbstractPaymentService { ### retrievePayment -This method is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Provider Service this method is used to retrieve the payment intent details from Stripe. +This method is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Processor this method is used to retrieve the payment intent details from Stripe. -This method accepts the `data` field of a Payment Session or a Payment. So, you should make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider. +This method accepts the `data` field of a Payment Session. So, you should make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider. This method must return an object containing the data from the third-party provider. @@ -249,21 +304,23 @@ import { Data } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... - async retrievePayment(paymentData: Data): Promise { + async retrievePayment( + paymentSessionData: Record + ): Promise | PaymentProcessorError> { return {} } } ``` -### getStatus +### getPaymentStatus This method is used to get the status of a Payment or a Payment Session. -Its main usage is in the place order workflow. If the status returned is not `authorized`, then the payment is considered failed, an error will be thrown, and the order will not be placed. +Its main usage is in the place order and create swap workflows. If the status returned is not `authorized`, then the payment is considered failed and an error will be thrown, stopping the task from completion. -This method accepts the `data` field of the Payment or Payment Session as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary. +This method accepts the `data` field of a Payment as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary. -This method returns a string that represents the status. The status must be one of the following values: +This method returns a string that represents the status. This string can be from the enum `PaymentSessionStatus` which can be imported from `@medusajs/medusa`. The status must be one of the following values: 1. `authorized`: The payment was successfully authorized. 2. `pending`: The payment is still pending. This is the default status of a Payment Session. @@ -271,7 +328,7 @@ This method returns a string that represents the status. The status must be one 4. `error`: If an error occurred with the payment. 5. `canceled`: If the payment was canceled. -An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider: +An example of a minimal implementation of `getPaymentStatus` where you don’t need to interact with the third-party provider: ```ts import { Data, PaymentSessionStatus } from "@medusajs/medusa" @@ -279,18 +336,14 @@ import { Data, PaymentSessionStatus } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... - async getStatus(data: Data): Promise { + async getPaymentStatus( + paymentSessionData: Record + ): Promise { return PaymentSessionStatus.AUTHORIZED } } ``` -:::note - -This code block assumes the status is stored in the `data` field as demonstrated in the `createPayment` method. - -::: - ### updatePayment This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a [line item is added to the cart](/api/store/#tag/Cart/operation/PostCartsCartLineItems) or when a [shipping method is selected](/api/store/#tag/Cart/operation/PostCartsCartShippingMethod). @@ -301,47 +354,35 @@ A line item refers to a product in the cart. ::: -It accepts the `data` field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type `PaymentContext` and has the following properties: +It accepts the `data` field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type `PaymentProcessorContext` and has the following properties: ```ts -type PaymentContext = { - cart: { - context: Record - id: string - email: string - shipping_address: Address | null - shipping_methods: ShippingMethod[] - } +type PaymentProcessorContext = { + billing_address?: Address | null + email: string currency_code: string amount: number - resource_id?: string + resource_id: string customer?: Customer + context: Record + paymentSessionData: Record } ``` -:::note - -Before v1.7.2, the second parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`. - -::: - You can utilize this method to interact with the third-party provider and update any details regarding the payment if necessary. This method must return an object of type `PaymentSessionResponse`. It should have the following properties: ```ts -type PaymentSessionResponse = { - update_requests: { - customer_metadata: Record +type PaymentProcessorSessionResponse = { + update_requests?: { + customer_metadata?: Record } session_data: Record } ``` -Where: - -- `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.md), the `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point. -- `update_requests` is an object that can be used to request from the Medusa core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer. +These are the same fields explained in the [initiatePayment](#initiatepayment) section. An example of a minimal implementation of `updatePayment`: @@ -357,9 +398,12 @@ import { class MyPaymentService extends AbstractPaymentService { // ... async updatePayment( - paymentSessionData: PaymentSessionData, - cart: Cart - ): Promise { + context: PaymentProcessorContext + ): Promise< + void | + PaymentProcessorError | + PaymentProcessorSessionResponse + > { // prepare data return { session_data, @@ -369,43 +413,16 @@ class MyPaymentService extends AbstractPaymentService { } ``` -### updatePaymentData - -This method is used to update the `data` field of a Payment Session. Particularly, it is called when a request is sent to the [Update Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) endpoint. This endpoint receives a `data` object in the body of the request that should be used to update the existing `data` field of the Payment Session. - -This method accepts the current `data` field of the Payment Session as the first parameter, and the new `data` field sent in the body request as the second parameter. - -You can utilize this method to interact with the third-party provider and make any necessary updates based on the `data` field passed in the body of the request. - -This method must return an object that will be stored in the `data` field of the Payment Session. - -An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session. - -```ts -import { Data, PaymentSessionData } from "@medusajs/medusa" -// ... - -class MyPaymentService extends AbstractPaymentService { - // ... - async updatePaymentData( - paymentSessionData: PaymentSessionData, - data: Data - ): Promise { - return updatedData - } -} -``` - ### deletePayment This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases: 1. When a request is sent to [delete the Payment Session](/api/store/#tag/Cart/operation/DeleteCartsCartPaymentSessionsSession). 2. When the [Payment Session is refreshed](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionsSession). The Payment Session is deleted so that a newer one is initialized instead. -3. When the Payment Provider is no longer available. This generally happens when the store operator removes it from the available Payment Provider in the admin. -4. When the region of the store is changed based on the cart information and the Payment Provider is not available in the new region. +3. When the Payment Processor is no longer available. This generally happens when the store operator removes it from the available Payment Processor in the admin. +4. When the region of the store is changed based on the cart information and the Payment Processor is not available in the new region. -It accepts the Payment Session as an object for its first parameter. +It accepts the `data` field of the payment session for its first parameter. You can use this method to interact with the third-party provider to delete data related to the Payment Session if necessary. @@ -418,18 +435,18 @@ import { PaymentSession } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... async deletePayment( - paymentSession: PaymentSession - ): Promise { - return + paymentSessionData: Record + ): Promise | PaymentProcessorError> { + return paymentSessionData } } ``` ### authorizePayment -This method is used to authorize payment using the Payment Session for an order. This is called when the [cart is completed](/api/store/#tag/Cart/operation/PostCartsCartComplete) and before the order is created. +This method is used to authorize payment using the Payment Session of an order. This is called when the [cart is completed](/api/store/#tag/Cart/operation/PostCartsCartComplete) and before the order is created. -This method is also used for authorizing payments of a swap of an order. +This method is also used for authorizing payments of a swap of an order and when authorizing sessions in a payment collection. The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed, the `authorizePayment` method will be called again to validate that the payment is now fully authorized. So, you should make sure to implement it for this case as well, if necessary. @@ -439,16 +456,20 @@ If the payment authorization fails, then an error will be thrown and the order w :::note -The payment authorization status is determined using the `getStatus` method as mentioned earlier. If the status is `requires_more` then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not `authorized`, then the payment is considered failed. +The payment authorization status is determined using the `getPaymentStatus` method as mentioned earlier. If the status is `requires_more` then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not `authorized`, then the payment is considered failed. ::: -This method accepts the Payment Session as an object for its first parameter, and a `context` object as a second parameter. The `context` object contains the following properties: +This method accepts the `data` field of a payment session for its first parameter, and a `context` object as a second parameter. The `context` object may contain the following properties: 1. `ip`: The customer’s IP. 2. `idempotency_key`: The [Idempotency Key](../payment.md#idempotency-key) that is associated with the current cart. It is useful when retrying payments, retrying checkout at a failed point, or for payments that require additional actions from the customer. +3. `cart_id`: The ID of a cart. This is only during operations like placing an order or creating a swap. -This method must return an object containing the property `status` which is a string that indicates the current status of the payment, and the property `data` which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s `status` and `data` fields respectively. +This method must return an object containing the following properties: + +- `status` which is a string that indicates the current status of the payment. +- `data` which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s `status` and `data` fields respectively. You can utilize this method to interact with the third-party provider and perform any actions necessary to authorize the payment. @@ -466,12 +487,15 @@ import { class MyPaymentService extends AbstractPaymentService { // ... async authorizePayment( - paymentSession: PaymentSession, - context: Data - ): Promise<{ - data: PaymentSessionData; - status: PaymentSessionStatus - }> { + paymentSessionData: Record, + context: Record + ): Promise< + PaymentProcessorError | + { + status: PaymentSessionStatus; + data: Record; + } + > { return { status: PaymentSessionStatus.AUTHORIZED, data: { @@ -482,39 +506,15 @@ class MyPaymentService extends AbstractPaymentService { } ``` -### getPaymentData - -After the payment is authorized using `authorizePayment`, a Payment instance will be created. The `data` field of the Payment instance will be set to the value returned from the `getPaymentData` method in the Payment Provider. - -This method accepts the Payment Session as an object for its first parameter. - -This method must return an object to be stored in the `data` field of the Payment instance. You can either use it as-is or make any changes to it if necessary. - -An example of a minimal implementation of `getPaymentData`: - -```ts -import { Data, PaymentSession } from "@medusajs/medusa" -// ... - -class MyPaymentService extends AbstractPaymentService { - // ... - async getPaymentData( - paymentSession: PaymentSession - ): Promise { - return paymentSession.data - } -} -``` - ### capturePayment This method is used to capture the payment amount of an order. This is typically triggered manually by the store operator from the admin. -This method is also used for capturing payments of a swap of an order. +This method is also used for capturing payments of a swap of an order, or when the [Capture Payment](/api/admin#tag/Payment/operation/PostPaymentsPaymentCapture) endpoint is called. You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment. -This method accepts the Payment as an object for its first parameter. +This method accepts the `data` field of the Payment for its first parameter. This method must return an object that will be stored in the `data` field of the Payment. @@ -538,11 +538,11 @@ class MyPaymentService extends AbstractPaymentService { This method is used to refund an order’s payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total order amount or part of it. -This method is also used for refunding payments of a swap of an order. +This method is also used for refunding payments of a swap or a claim of an order, or when the [Refund Payment](/api/admin#tag/Payment/operation/PostPaymentsPaymentRefunds) endpoint is called. You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment. -This method accepts the Payment as an object for its first parameter, and the amount to refund as a second parameter. +This method accepts the `data` field of a Payment for its first parameter, and the amount to refund as a second parameter. This method must return an object that is stored in the `data` field of the Payment. @@ -555,9 +555,9 @@ import { Data, Payment } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... async refundPayment( - payment: Payment, + paymentSessionData: Record, refundAmount: number - ): Promise { + ): Promise | PaymentProcessorError> { return { id: "test", } @@ -571,14 +571,11 @@ This method is used to cancel an order’s payment. This method is typically tri 1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled. 2. If the store operator cancels the order from the admin. - -This method is also used for canceling payments of a swap of an order. +3. When the payment of an order's swap is canceled. You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment. -This method accepts the Payment as an object for its first parameter. - -This method must return an object that is stored in the `data` field of the Payment. +This method accepts the `data` field of the Payment for its first parameter. An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider: @@ -588,7 +585,9 @@ import { Data, Payment } from "@medusajs/medusa" class MyPaymentService extends AbstractPaymentService { // ... - async cancelPayment(payment: Payment): Promise { + async cancelPayment( + paymentSessionData: Record + ): Promise | PaymentProcessorError> { return { id: "test", } @@ -598,57 +597,7 @@ class MyPaymentService extends AbstractPaymentService { --- -## Optional Methods - -### retrieveSavedMethods - -This method can be added to your Payment Provider service if your third-party provider supports saving the customer’s payment methods. Please note that in Medusa there is no way to save payment methods. - -This method is called when a request is sent to [Retrieve Saved Payment Methods](/api/store/#tag/Customer/operation/GetCustomersCustomerPaymentMethods). - -This method accepts the customer as an object for its first parameter. - -This method returns an array of saved payment methods retrieved from the third-party provider. You have the freedom to shape the items in the array as you see fit since they will be returned as-is for the response to the request. - -:::note - -If you’re using Medusa’s [Next.js](../../../starters/nextjs-medusa-starter.mdx) storefront starter, note that the presentation of this method is not implemented. You’ll need to implement the UI and pages for this method based on your implementation and the provider you are using. - -::: - -An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider: - -```ts -import { Customer, Data } from "@medusajs/medusa" -// ... - -class MyPaymentService extends AbstractPaymentService { - // ... - /** - * Fetches a customers saved payment methods - * if they're saved in Stripe. - * @param {object} customer - customer to fetch saved cards for - * @return {Promise>} saved payments methods - */ - async retrieveSavedMethods( - customer: Customer - ): Promise { - if (customer.metadata && customer.metadata.stripe_id) { - const methods = await this.stripe_.paymentMethods.list({ - customer: customer.metadata.stripe_id, - type: "card", - }) - - return methods.data - } - - return Promise.resolve([]) - } -} -``` - ---- - ## See Also -- Implementation Examples: [Stripe](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-paypal) payment providers. +- Implementation Examples: [Stripe](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-paypal) Payment Processors. +- [Implement checkout flow on the storefront](../storefront/implement-checkout-flow.mdx). diff --git a/docs/content/modules/carts-and-checkout/overview.mdx b/docs/content/modules/carts-and-checkout/overview.mdx index edd410f467..b151bc2804 100644 --- a/docs/content/modules/carts-and-checkout/overview.mdx +++ b/docs/content/modules/carts-and-checkout/overview.mdx @@ -40,7 +40,7 @@ Customers can manage their cart including adding, updating, and removing items f Developers can integrate any third-party provider or custom logic to offer shipping and payment options for customers during checkout. They can integrate them using existing plugins or by creating their own. -Admins can specify available shipping and payment providers during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx). +Admins can specify available shipping and payment processors during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx). -```tsx +```ts import { useCart } from "medusa-react" const Cart = () => { @@ -252,7 +252,7 @@ medusa.carts.addShippingMethod(cartId, { -```tsx +```ts import { useAddShippingMethodToCart } from "medusa-react" // ... @@ -303,11 +303,11 @@ It returns the updated cart, with the created shipping method available in the a ## Payment Step -In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment providers is done differently for each provider, so this section will just show the general steps you should follow when implementing this step. +In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment processors is done differently for each processor, so this section will just show the general steps you should follow when implementing this step. -### Initialize Payment Sessions +### Display Payment Methods -When the page opens and before the payment providers are displayed to the customer to choose from, you must initialize the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with. +When the page opens and before the payment providers are displayed to the customer to choose from, you must create the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with. To initialize the payment sessions, send a `POST` request to the [Initialize Payment Sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) API endpoint: @@ -339,7 +339,7 @@ const PaymentProviders = () => { return (
{!cart?.payment_sessions.length && ( - No payment providers + No payment processors )}
    {cart?.payment_sessions.map( @@ -378,7 +378,7 @@ This endpoint accepts the ID of the cart as a path parameter. It returns the upd ### Select Payment Session -When the customer chooses the payment provider they want to complete purchase with, you should select the payment session associated with that payment provider. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint: +When the customer chooses the payment processor they want to complete purchase with, you should select the payment session associated with that payment processor. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint: @@ -396,7 +396,7 @@ medusa.carts.setPaymentSession(cartId, { -```tsx +```ts import { useCart } from "medusa-react" const PaymentProviders = () => { @@ -439,19 +439,19 @@ fetch(`/store/carts/${cartId}/payment-session`, { -The request accepts the ID of the cart as a path parameter, and the ID of the payment provider in the request's body. +The request accepts the ID of the cart as a path parameter, and the ID of the payment processor in the request's body. It returns the updated cart, with the selected payment session available under `cart.payment_session`. :::tip -If you have one payment provider or if only one payment provider is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not. +If you have one payment processor or if only one payment processor is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not. ::: ### Update Payment Session -This step is optional and is only necessary for some payment providers. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Provider to perform payment operations such as capturing payment. +This step is optional and is only necessary for some payment processors. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Processor to perform payment operations such as capturing payment. If you need to update that data at any point before the purchase is made, send a request to [Update a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) API endpoint: @@ -474,7 +474,7 @@ medusa.carts.updatePaymentSession(cartId, paymentProviderId, { -```tsx +```ts import { useUpdatePaymentSession, useCart } from "medusa-react" // ... @@ -531,13 +531,13 @@ fetch( -This request accepts the ID of the cart and the ID of the payment session's payment provider as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment provider. +This request accepts the ID of the cart and the ID of the payment session's payment processor as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment processor. It returns the updated cart. You can access the payment session's data on `cart.payment_session.data`. ### Complete Cart -The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment provider first to make sure the authorization is successful when you send the request to complete the cart. +The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment processor first to make sure the authorization is successful when you send the request to complete the cart. To complete a cart, send a `POST` request to the [Complete a Cart](/api/store/#tag/Cart/operation/PostCartsCartComplete) API endpoint: @@ -554,7 +554,7 @@ medusa.carts.complete(cartId) -```tsx +```ts import { useCart } from "medusa-react" // ... diff --git a/docs/content/modules/customers/admin/manage-customers.mdx b/docs/content/modules/customers/admin/manage-customers.mdx index a43bf9ab28..e22eb8716c 100644 --- a/docs/content/modules/customers/admin/manage-customers.mdx +++ b/docs/content/modules/customers/admin/manage-customers.mdx @@ -195,9 +195,9 @@ export default CreateCustomer fetch(`/admin/customers`, { method: "POST", credentials: "include", - headers: { - "Content-Type": "application/json", - }, + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ email, password, diff --git a/docs/content/modules/gift-cards/backend/send-gift-card-to-customer.md b/docs/content/modules/gift-cards/backend/send-gift-card-to-customer.md index 979c8e465f..a5c708914f 100644 --- a/docs/content/modules/gift-cards/backend/send-gift-card-to-customer.md +++ b/docs/content/modules/gift-cards/backend/send-gift-card-to-customer.md @@ -27,11 +27,7 @@ You can alternatively use the [SendGrid](../../../plugins/notifications/sendgrid ### Medusa Components -It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis). +It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### Notification Provider diff --git a/docs/content/modules/multiwarehouse/install-modules.md b/docs/content/modules/multiwarehouse/install-modules.md new file mode 100644 index 0000000000..6de84644af --- /dev/null +++ b/docs/content/modules/multiwarehouse/install-modules.md @@ -0,0 +1,87 @@ +--- +description: "In this document, you’ll learn how to install multi-warehouse related modules using NPM in the Medusa backend." +--- + +# Install Multi-Warehouse Modules + +In this document, you’ll learn how to install multi-warehouse related modules using NPM in the Medusa backend. + +:::tip + +You can also install these modules in any NPM project. + +::: + +## Inventory Module + +### Step 1: Install Inventory Module + +To install the Inventory Module, run the following command in the root directory of the Medusa backend: + +```bash npm2yarn +npm install @medusajs/inventory +``` + +### Step 2: Add Inventory Module to Configurations + +In `medusa-config.js`, add the inventory module to the exported object under the `modules` property: + +```js +module.exports = { + // ... + modules: { + // ... + inventoryService: { + resolve: "@medusajs/inventory", + }, + }, +} +``` + +### Step 3: Run Migrations of Inventory Module + +Run the following command to reflect schema changes into your database: + +```bash +medusa migrations run +``` + +You can now start the Medusa backend and use the inventory module in your commerce application. + +--- + +## Stock Location Module + +### Step 1: Install Stock Location Module + +To install the Stock Location Module, run the following command in the root directory of the Medusa backend: + +```bash npm2yarn +npm install @medusajs/stock-location +``` + +### Step 2: Add Stock Location Module to Configurations + +In `medusa-config.js`, add the stock location module to the exported object under the `modules` property: + +```js +module.exports = { + // ... + modules: { + // ... + stockLocationService: { + resolve: "@medusajs/stock-location", + }, + }, +} +``` + +### Step 3: Run Migrations of Stock Location Module + +Run the following command to reflect schema changes into your database: + +```bash +medusa migrations run +``` + +You can now start the Medusa backend and use the stock location module in your commerce application. diff --git a/docs/content/modules/multiwarehouse/inventory-module.md b/docs/content/modules/multiwarehouse/inventory-module.md new file mode 100644 index 0000000000..b2a4c1128d --- /dev/null +++ b/docs/content/modules/multiwarehouse/inventory-module.md @@ -0,0 +1,102 @@ +--- +description: "In this document, you’ll learn about the inventory module, how it works, and its relation to other processes in your commerce application." +--- + +# Inventory Module + +In this document, you’ll learn about the inventory module and how it works. + +## Overview + +The inventory module includes all functionalities related to product inventory. It implements inventory management for a product, confirming whether a product is available across inventory levels, and updating the inventory availability of a product variant at different points in the order lifecycle. + +Medusa's Inventory module is a standalone module that can be used in any commerce application, not just in a Medusa backend. This document gives a general overview of how the inventory module is designed, then explains how the Medusa core orchestrates relations and processes around this module when it's used with the Medusa backend. + +--- + +## Entities Overview + +![Inventory Module Entities Diagram](https://res.cloudinary.com/dza7lstvk/image/upload/v1680184977/Medusa%20Docs/Diagrams/inventory-diagram_1_eaupf2.jpg) + +### InventoryItem + +The `InventoryItem` entity represents a stock-kept item. It holds inventory details, such as `origin_country` or `hs_code`. It's not associated with any entity that represents a stock-kept item, enabling you to choose how you integrate it with your existing commerce application. + +The `InventoryItem` entity doesn’t hold data regarding where the item is stored or its available quantity. This data is stored in the `InventoryLevel` entity. + +### InventoryLevel + +The `InventoryLevel` entity represents the quantity of an inventory item in a stock location. This ensures that an inventory item can be located in multiple stock locations. + +Among the `InventoryLevel` entity’s attributes, the following provide insights into the available quantity within that entity: + +- `stocked_quantity`: a number indicating the available stock of that item in the associated location. +- `reserved_quantity`: a number indicating the quantity that should be reserved from the available `stocked_quantity`. It can be used to indicate a quantity that is still not removed from stock, but should be considered as unavailable which confirming that an item is in stock. +- `incoming_quantity`: a number indicating an incoming stock quantity of the item into the associated location. This attribute doesn't play into the `stocked_quantity` or when confirming whether a stock-kept item is in stock. + +The `InventoryLevel` entity is associated with a location through the `location_id` attribute. The entity representing a location isn't implemented with this module, allowing for greater flexibility in how you choose to implement a location. + +### ReservationItem + +The `ReservationItem` entity represents a quantity of an inventory item that is reserved when an order is placed but not fulfilled yet. This indicates that the stock-kept item still hasn't been moved from stock, but should be considered as unavailable. + +The `ReservationItem` entity has the following notable attributes, among others: + +- `line_item_id`: The ID of the line item in the order that this reservation item refers to. +- `inventory_item_id`: The ID of the inventory item this reservation item refers to. +- `location_id`: The ID of the location that this item is reserved from. +- `quantity`: A number indicating the quantity to be reserved. + +--- + +## How the Module Integrates into Medusa + +This section explains how the Medusa backend uses the inventory module along with its entities and other modules, and in its processes. + +### Entities Relation Overview + +The core Medusa package contains an entity `ProductVariantInventoryItem` that is used to establish a relation between a product variant and an inventory item. This enables you to use inventory management features on the product variant level, while maintaining the modularity that allows you to use Medusa's inventory module or implement your custom inventory module. + +When you use Medusa's Inventory Module, the Medusa backend uses the `ProductVariantInventoryItem` entity as a bridge between the `InventoryItem` entity and the `ProductVariant` entity. + +![Inventory Item's Relation to Product Variants in the Medusa Backend](https://res.cloudinary.com/dza7lstvk/image/upload/v1680185070/Medusa%20Docs/Diagrams/inventory-item-medusa-diagram_i21ht8.jpg) + +The Medusa backend also orchestrates between the installed inventory and stock location modules. The association between an Inventory Level and a location is handled by passing the ID of a location from the stock location module to the inventory module when an Inventory Level is being created. When using Medusa's [Stock Location module](./stock-location-module.md), the entity representing the location is `StockLocation`. + +![Inventory Level's relation to Stock Location Module in the Medusa Backend](https://res.cloudinary.com/dza7lstvk/image/upload/v1680185151/Medusa%20Docs/Diagrams/inventory-medusa-diagram_ltojt9.jpg) + +Similarly, the Medusa backend associates the `ReservationItem` entity with a line item and a location by passing the IDs of each to the inventory module when a reservation item is created. + +### Product Variant Creation Process + +In the Medusa backend, when a product variant that has an enabled `manage_inventory` attribute is created, the backend uses the inventory module to automatically create an inventory item along with the product variant. When the inventory item is created, the Medusa backend attaches it to the product variant using the `ProductVariantInventoryItem` entity as explained earlier. + +The Medusa backend uses the inventory module to create Inventory Levels when the admin sets the available quantity of a product variant in a stock location. + +### Cart and Checkout + +During the cart and checkout workflow, for example when a product variant is added to the cart or during cart validation, the Medusa backend uses the inventory module to confirm that items in the cart have sufficient stock to be purchased in the desired quantity. If a product variant doesn't have an inventory item, which is the case when the `manage_inventory` attribute of the variant is disabled, the variant is assumed to be available in stock. + +As an inventory item can exist in multiple locations, the inventory module checks across those locations. The Medusa backend retrieves the locations based on the sales channel of the cart, as each location is associated with a sales channel, and passes them along to the inventory module to perform the checking. + +:::tip + +You can learn more about the relation between Stock Locations and Sales Channels in the [Stock Location documentation](./stock-location-module.md). + +::: + +Then, the inventory module confirms that the product variant has sufficient quantity across these locations by summing all the `stocked_quantity` of the inventory levels associated with these locations. When retrieving the `stocked_quantity` of each of the inventory levels, the `reserved_quantity` is subtracted from it to ensure accurate availability. + +### Order Placement + +When an order is placed, the Medusa backend uses the inventory module to reserve the ordered quantity of line items that are associated with product variants having an enabled `manage_inventory` attribute. The reserved quantity is indicated by creating a reservation item for each line item, associating it with its inventory item and a stock location. + +The Medusa backend chooses the stock location randomly from the available stock locations associated with the order’s sales channel. The admin can later change which stock location the item will be fulfilled from. + +### Order Fulfillment + +When an item in the order is fulfilled, and the item is associated with a product variant that has an enabled `manage_inventory`, the Medusa backend uses the inventory module to subtract the inventory level's `reserved_quantity` from the `stocked_quantity`. The inventory module also resets the `reserved_quantity` to `0`. + +### Order Return + +When an item in the order is returned, and the item is associated with a product variant that has an enabled `manage_inventory`, the Medusa backend uses the inventory module to increment the inventory level's `stocked_quantity` with the returned amount. diff --git a/docs/content/modules/multiwarehouse/overview.mdx b/docs/content/modules/multiwarehouse/overview.mdx new file mode 100644 index 0000000000..f15a96bbd6 --- /dev/null +++ b/docs/content/modules/multiwarehouse/overview.mdx @@ -0,0 +1,168 @@ +--- +description: "Multi-warehouse allows merchants to store a product in multiple locations with accurate and consistent inventory data within the commerce application." +--- + +import DocCardList from '@theme/DocCardList'; +import DocCard from '@theme/DocCard'; +import Icons from '@theme/Icon'; + +# Multi-Warehouse + +Multi-warehouse allows merchants to store a product in multiple locations with accurate and consistent inventory data within the commerce application. + +Multi-warehouse in Medusa is composed of two modules: an inventory module - which is the NPM package `@medusajs/inventory` - and a stock location module - which is the NPM package `@medusajs/stock-location`. + + + +--- + +## Features + +### Multiple Stock Locations + +Admins can manage the stock locations, which are the places they store their products. Stock locations are associated with different sales channels. + + + +### Inventory Management Across Locations + +Admins can manage the inventory of product variants across the different stock locations. + + + +### Manage Item Allocations in Orders + +Admins can manage item allocations to choose which stock location to fulfill items from or return items to. Item quantities are reserved in a stock location until the item is fulfilled to ensure data consistency. + + + +--- + +## Understanding the Architecture + +The commerce modules automatically detect whether a product variant is in stock, decrement the variant’s stock on fulfillment, and increment the variant’s stock on returns. + + + +--- + +## Related Modules + +Discover Multi-warehouse’s relation to other modules in Medusa. + + \ No newline at end of file diff --git a/docs/content/modules/multiwarehouse/stock-location-module.md b/docs/content/modules/multiwarehouse/stock-location-module.md new file mode 100644 index 0000000000..8eb795bc3b --- /dev/null +++ b/docs/content/modules/multiwarehouse/stock-location-module.md @@ -0,0 +1,63 @@ +--- +description: "In this document, you’ll learn about the inventory module, how it works, and its relation to other processes in your commerce application." +--- + +# Stock Level Module + +In this document, you’ll learn about the Stock Level module and how it works. + +## Overview + +A stock location indicates a physical address that stock-kept items can be stored in. The stock location module handles functionalities related to managing stock locations and their addresses. + +Medusa's Stock Location module is a standalone module that can be used in any commerce application, not just in a Medusa backend. This document gives a general overview of how the stock location module is designed, and highlights how the Medusa core orchestrates relations around this module when it's used with the Medusa backend. + +--- + +## StockLocation Entity + +The `StockLocation` entity represents a stock location. It has minimal attributes including a `name` attribute. It’s associated with the `StockLocationAddress` entity. + +--- + +## StockLocationAddress Entity + +The `StockLocationAddress` is an entitiy that contains address-related fields, such as `city` or `country_code`. + +The `StockLocationAddress` entity belongs to the `StockLocation` entity. It is used to store the address details of a stock location. + +--- + +## How the Module Integrates into Medusa + +This section explains how the Medusa backend uses the stock location module along with its entities and other modules. + +### Entities Relation Overview + +The following entities in the Medusa backend each have an attribute that is used to associate them with a stock location: + +- `Fulfillment`: The `location_id` attribute within the `Fulfillment` entity is used to indicate from which stock location an order item is fulfilled. +- `Return`: The `location_id` attribute within the `Return` entity is used to indicate to which stock location an order item is returned. +- `Store`: The `default_location_id` attribute within the `Store` entity is used to indicate the default stock location to use in the ecommerce store. +- `SalesChannelLocation`: This entity is used to attach a stock location to a `SalesChannel`. The relation between these two entities is explained further in the [Relation to Sales Channel section](#relation-to-saleschannel). + +When the Medusa's Stock Location module is used with the Medusa backend, the ID that is associated with the attributes mentioned above is from the `StockLocation` module. + +The Medusa backend also orchestrates between different modules. The [Inventory Module](./inventory-module.md)'s entities contain the following attributes to handle associations between them and a stock location: + +- `InventoryLevel`: This entity is used to indicate the stocked quantity of an inventory item in a stock location. As explained in the [Inventory Module documentation](./inventory-module.md#inventorylevel), the `InventoryLevel` entity has an attribute `location_id`. +- `ReservationItem`: This entity is used to indicate the reserved quantity of an inventory item in a stock location. As explained in the [Inventory Module documentation](./inventory-module.md#reservationitem), the `ReservationItem` entity has an attribute `location_id`. + +When both modules are used within the Medusa backend, the Medusa backend bridges between these modules by passing the ID of a `StockLocation` from the stock location module to the inventory module, and the inventory module uses the ID in its entities. + +### Relation to SalesChannel + +A stock location can be associated with more than one sales channel. For example, a physical store and an online store can use the same stock location. + +As the `StockLocation` and `SalesChannel` entities are available in separate modules, the Medusa backend handles attaching the stock location with the sales channel within the `SalesChannelLocation` entity. + +This relation is used across the Medusa backend and within checkout and order workflows: + +- When checking the availability of an inventory item during checkout, the Medusa backend retrieves the location IDs that are associated with the cart’s sales channel using the `SalesChannelLocation`, then passes it along to the inventory module to perform the quantity check. +- When an order is placed, the Medusa backend retrieves the location IDs that are associated with the cart’s sales channel using the `SalesChannelLocation` entity, and passes it to the inventory module that reserves the ordered quantity of the inventory item from that location. The admin can later change the stock location if necessary. +- When an item in an order is fulfilled, the admin chooses a stock location to fulfill the item from. Similarly, when an item in an order is returned, the admin can choose a stock location to return the item to. The Medusa backend then passes the ID of the location from the stock module to the inventory module to perform inventory management functionalities. diff --git a/docs/content/modules/orders/backend/handle-order-claim-event.md b/docs/content/modules/orders/backend/handle-order-claim-event.md index 23e6100233..93edc6187b 100644 --- a/docs/content/modules/orders/backend/handle-order-claim-event.md +++ b/docs/content/modules/orders/backend/handle-order-claim-event.md @@ -25,11 +25,7 @@ In this document, you’ll learn how to handle the `order-update-token.created` ### Medusa Components -It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis). +It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### Notification Provider diff --git a/docs/content/modules/orders/storefront/handle-order-edits.mdx b/docs/content/modules/orders/storefront/handle-order-edits.mdx index c3e60c6a80..13c5cf7733 100644 --- a/docs/content/modules/orders/storefront/handle-order-edits.mdx +++ b/docs/content/modules/orders/storefront/handle-order-edits.mdx @@ -191,14 +191,14 @@ If `difference_due` is less than 0, then the amount will be refunded to the cust :::note -💡 This section explains how to authorize the payment using one payment provider and payment session. However, payment collections allow customers to pay in installments or with more than one provider. You can learn more about how to do that using the [batch endpoints of the Payment APIs](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsBatchAuthorize) +💡 This section explains how to authorize the payment using one payment processor and payment session. However, payment collections allow customers to pay in installments or with more than one provider. You can learn more about how to do that using the [batch endpoints of the Payment APIs](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsBatchAuthorize) ::: If `difference_due` is greater than 0, then additional payment from the customer is required. In this case, you must implement these steps to allow the customer to authorize the payment: -1. Show the customer the available payment providers. These can be retrieved from the details of [the region of the order](/api/store/#tag/Region/operation/GetRegionsRegion). -2. When the customer selects the payment provider, initialize the payment session of that provider in the payment collection. You can do that by sending a request to the [Manage Payment Sessions](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessions) endpoint, passing it the payment collection’s ID as a path parameter, and the payment provider’s ID as a request body parameter: +1. Show the customer the available payment processors. These can be retrieved from the details of [the region of the order](/api/store/#tag/Region/operation/GetRegionsRegion). +2. When the customer selects the payment processor, initialize the payment session of that provider in the payment collection. You can do that by sending a request to the [Manage Payment Sessions](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessions) endpoint, passing it the payment collection’s ID as a path parameter, and the payment processor's ID as a request body parameter: @@ -267,7 +267,7 @@ fetch( 1. Show the customer the payment details form based on the payment session’s provider. For example, if the provider ID of a payment session is `stripe`, you must show Stripe’s card component to enter the customer’s card details. -2. Authorize the payment using the payment provider. The [Authorize Payment Session](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsSessionAuthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters: +2. Authorize the payment using the payment processor. The [Authorize Payment Session](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsSessionAuthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters: diff --git a/docs/content/modules/overview.mdx b/docs/content/modules/overview.mdx index f2357f501f..247a9e3ba8 100644 --- a/docs/content/modules/overview.mdx +++ b/docs/content/modules/overview.mdx @@ -39,6 +39,21 @@ Medusa provides the necessary features to build a customizable shopping experien - Create draft orders without direct involvement from the customer. + + + + - Create and manage multiple stock locations that represent where you physically store your products. + - Manage the product variants' inventory across stock locations, and associate those locations with sales channels. + - Associate order fulfillemnts and returns with a stock location. The management of inventory item quantity is handled automatically. + + - - Accept payments with payment providers including Stripe and PayPal. + - Accept payments with payment processors including Stripe and PayPal. - Calculate taxes of a cart through custom logic or third-party tax providers. - Allow customers to apply discount codes and gift cards during checkout. - Fully customize the frontend experience of the checkout process. @@ -144,7 +159,7 @@ Medusa's multi-region setup and sales channels allow businesses to sell internat > - Create an unlimited number of regions, each associated with a currency and at least one country. - - Manage each region’s settings including payment providers, shipping options, and more. + - Manage each region’s settings including payment processors, shipping options, and more. - Set prices for products and shipping options specific to a currency or a region. @@ -211,10 +226,10 @@ If you have any questions about Medusa, its features, and development with it, f { type: 'link', href: '/modules/carts-and-checkout/backend/add-payment-provider', - label: 'Create a Payment Provider', + label: 'Create a Payment Processor', customProps: { icon: Icons['credit-card-solid'], - description: 'Create a payment provider.', + description: 'Learn how to create a payment processor.', }, }, { @@ -244,4 +259,4 @@ If you have any questions about Medusa, its features, and development with it, f description: 'Manage sales channels.', } } -]} /> \ No newline at end of file +]} /> diff --git a/docs/content/modules/price-lists/admin/import-prices.mdx b/docs/content/modules/price-lists/admin/import-prices.mdx index de12ed0fd0..11fed8f5e6 100644 --- a/docs/content/modules/price-lists/admin/import-prices.mdx +++ b/docs/content/modules/price-lists/admin/import-prices.mdx @@ -26,11 +26,7 @@ Importing prices into a price list removes all existing prices in the price list ### Medusa Components -It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis). +It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### File Service Plugin diff --git a/docs/content/modules/products/admin/import-products.mdx b/docs/content/modules/products/admin/import-products.mdx index 9aaace58aa..767b9c9602 100644 --- a/docs/content/modules/products/admin/import-products.mdx +++ b/docs/content/modules/products/admin/import-products.mdx @@ -20,11 +20,7 @@ Using Medusa’s [Batch Job Admin APIs](/api/admin/#tag/Batch-Job), you can impo ### Medusa Components -It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. - -### Redis - -Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis). +It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### File Service Plugin diff --git a/docs/content/modules/products/admin/manage-categories.mdx b/docs/content/modules/products/admin/manage-categories.mdx new file mode 100644 index 0000000000..becd2b209e --- /dev/null +++ b/docs/content/modules/products/admin/manage-categories.mdx @@ -0,0 +1,678 @@ +--- +description: 'Learn how to how to manage product categories using the admin REST APIs. Learn how to create, update, and delete categories, and how to manage products in a category.' +addHowToData: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# How to Manage Product Categories + +In this document, you’ll learn how to manage product categories using the admin REST APIs. + +## Overview + +Using the product category admin REST APIs, you can manage the categories in your commerce application. + +### Scenario + +You want to add or use the following admin functionalities: + +- Manage categories including list, create, edit, and delete categories. +- Manage products in a category, including adding or removing products from a category. + +--- + +## Prerequisites + +### Medusa Components + +It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. + +### JS Client + +This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods. + +If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client](../../../js-client/overview.md) installed and have [created an instance of the client](../../../js-client/overview.md#configuration). + +### Medusa React + +This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods. + +If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage). + +### Authenticated Admin User + +You must be an authenticated admin user before following along with the steps in the tutorial. + +You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication). + +--- + +## List Categories + +You can retrieve available categories by sending a request to the [List Categories](/api/admin#tag/Product-Category/operation/GetProductCategories) endpoint: + + + + +```ts +medusa.admin.productCategories.list() +.then(({ product_categories, limit, offset, count }) => { + console.log(product_categories.length) + // display categories +}) +``` + + + + +```tsx +import { useAdminProductCategories } from "medusa-react" +import { ProductCategory } from "@medusajs/medusa" + +function Categories() { + const { + product_categories, + isLoading } = useAdminProductCategories() + + return ( +
    + {isLoading && Loading...} + {product_categories && !product_categories.length && ( + No Product + )} + {product_categories && product_categories.length > 0 && ( +
      + {product_categories.map( + (category: ProductCategory) => ( +
    • {category.name}
    • + ) + )} +
    + )} +
    + ) +} + +export default Categories +``` + +
    + + +```ts +fetch(`/admin/product-categories`, { + credentials: "include", +}) +.then((response) => response.json()) +.then(({ product_categories, limit, offset, count }) => { + console.log(product_categories.length) + // display product categories +}) +``` + + + + +```bash +curl -L -X GET '/admin/product-categories' \ +-H 'Authorization: Bearer ' +``` + + +
    + +This request returns an array of product categories, as well as [pagination fields](/api/admin#section/Pagination). + +You can also pass filters and other selection query parameters to the request. Check out the [API reference](/api/admin#tag/Product-Category/operation/GetProductCategories) for more details on available query parameters. + +--- + +## Create a Category + +You can create a category by sending a request to the [Create a Category](/api/admin#tag/Product-Category/operation/PostProductCategories) endpoint: + + + + +```ts +medusa.admin.productCategories.create({ + name: "Skinny Jeans", +}) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```tsx +import { useAdminCreateProductCategory } from "medusa-react" + +const CreateCategory = () => { + const createCategory = useAdminCreateProductCategory() + // ... + + const handleCreate = () => { + createCategory.mutate({ + name: "Skinny Jeans", + }) + } + + // ... +} + +export default CreateCategory +``` + + + + +```ts +fetch(`/admin/product-categories`, { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "Skinny Jeans", + }), +}) +.then((response) => response.json()) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```bash +curl -L -X POST '/admin/product-categories' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "name": "Skinny Jeans", +}' +``` + + + + +This request requires one body parameter `name`, which is the name of the category. The request also accepts the following optional body parameters: + +- `handle`: a string that is typically used as a URL path or slug. If you don’t provide a `handle`, it will be the kebab case format of the name. +- `is_internal`: a boolean that indicates whether the category is visible to customers. By default, it’s `false`. +- `is_active`: a boolean that indicates whether the category is active. By default, it’s `true`. +- `parent_category_id`: An ID of another category that this new category should be a child of. + +The request returns the newly created product category. + +--- + +## Retrieve a Category + +You can retrieve a product category by sending a request to the [Get a Product Category](/api/admin#tag/Product-Category/operation/GetProductCategoriesCategory) endpoint: + + + + +```ts +medusa.admin.productCategories.retrieve(productCategoryId) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```tsx +import { useAdminProductCategory } from "medusa-react" + +const Category = () => { + const { + product_category, + isLoading, + } = useAdminProductCategory(productCategoryId) + + return ( +
    + {isLoading && Loading...} + {product_category && {product_category.name}} + +
    + ) +} + +export default Category +``` + +
    + + + + +```ts +fetch(`/admin/product-categories/${productCategoryId}`, { + credentials: "include", +}) +.then((response) => response.json()) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```bash +curl -L -X GET '/admin/product-categories/' \ +-H 'Authorization: Bearer ' +``` + + +
    + +This request requires the ID of the category to be passed as a path parameter. + +It returns the full object of the product category. + +--- + +## Edit a Category + +You can edit a product category by sending a request to the [Update a Product Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory) endpoint: + + + + +```ts +medusa.admin.productCategories.update(productCategoryId, { + name: "Skinny Jeans", +}) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```tsx +import { useAdminUpdateProductCategory } from "medusa-react" + +const UpdateCategory = () => { + const updateCategory = useAdminUpdateProductCategory( + productCategoryId + ) + // ... + + const handleUpdate = () => { + updateCategory.mutate({ + name: "Skinny Jeans", + }) + } + + // ... +} + +export default UpdateCategory +``` + + + + + + +```ts +fetch(`/admin/product-categories/${productCategoryId}`, { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: "Skinny Jeans", + }), + }) + .then((response) => response.json()) + .then(({ product_category }) => { + console.log(product_category.id) + }) +``` + + + + +```bash +curl -L -X POST '/admin/product-categories/' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "name": "Skinny Jeans", +}' +``` + + + + +This request requires the ID of the category to be passed as a path parameter. + +In its body, you can pass any of the category’s fields that you want to update. In the code snippet above, you update the name of the category. + +You can check the list of accepted fields in the [API reference](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory). + +The request returns the full object of the updated product category. + +--- + +## Manage Products in a Category + +:::note + +You can manage the categories of each product individually using the [product APIs](/api/admin#tag/Product). This section explores the other approach of managing a product’s category using the product category APIs. + +::: + +### Add Products to a Category + +You can add more than one product to a category using the [Add Products to a Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategoryProductsBatch) endpoint: + + + + +```ts +medusa.admin.productCategories.addProducts(productCategoryId, { + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], +}) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```tsx +import { useAdminAddProductsToCategory } from "medusa-react" + +const UpdateProductsInCategory = () => { + const addProductsToCategory = useAdminAddProductsToCategory( + productCategoryId + ) + // ... + + const handleAdd = () => { + addProductsToCategory.mutate({ + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], + }) + } + + // ... +} + +export default UpdateProductsInCategory +``` + + + + + + +```ts +fetch(`/admin/product-categories/${productCategoryId}/products/batch`, { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], + }), +}) +.then((response) => response.json()) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```bash +curl -L -X POST '/admin/product-categories//products/batch' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "product_ids": [ + { + "id": "productId1" + }, + { + "id": "productId2" + } + ] +}' +``` + + + + +This request requires the ID of the category to be passed as a path parameter. + +In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to add. So, each product you want to add you pass it to the array as an object having the property `id`. + +The request returns the full object of the product category updated with the new products. + +### Remove Products from a Category + +You can remove products from a category by sending a request to the [Delete Products](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategoryProductsBatch) endpoint: + + + + +```ts +medusa.admin.productCategories.removeProducts( + productCategoryId, + { + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], + } +) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```tsx +import { + useAdminDeleteProductsFromCategory, +} from "medusa-react" + +const DeleteProductsFromCategory = () => { + const deleteProductsFromCategory = + useAdminDeleteProductsFromCategory(productCategoryId) + // ... + + const handleDelete = () => { + deleteProductsFromCategory.mutate({ + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], + }) + } + + // ... +} + +export default DeleteProductsFromCategory +``` + + + + + + +```ts +fetch(`/admin/product-categories/${productCategoryId}/products/batch`, { + credentials: "include", + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + product_ids: [ + { + id: productId1, + }, + { + id: productId2, + }, + ], + }), +}) +.then((response) => response.json()) +.then(({ product_category }) => { + console.log(product_category.id) +}) +``` + + + + +```bash +curl -L -X DELETE '/admin/product-categories//products/batch' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "product_ids": [ + { + "id": "productId1" + }, + { + "id": "productId2" + } + ] +}' +``` + + + + +This request requires the ID of the category to be passed as a path parameter. + +In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to remove. So, each product you want to remove you pass it to the array as an object having the property `id`. + +The request returns the full object of the product category updated with the its products. + +--- + +## Delete a Category + +You can delete a product category by sending a request to the [Delete a Product Category](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategory) endpoint: + + + + +```ts +medusa.admin.productCategories.delete(productCategoryId) +.then(({ id, object, deleted }) => { + console.log(id) +}) +``` + + + + +```tsx +import { useAdminDeleteProductCategory } from "medusa-react" + +const Category = () => { + const deleteCategory = useAdminDeleteProductCategory( + productCategoryId + ) + // ... + + const handleDelete = () => { + deleteCategory.mutate() + } + + // ... +} + +export default Category +``` + + + + + + +```ts +fetch(`/admin/product-categories/${productCategoryId}`, { + credentials: "include", + method: "DELETE", +}) +.then((response) => response.json()) +.then(({ id, object, deleted }) => { + console.log(id) +}) +``` + + + + +```bash +curl -L -X DELETE '/admin/product-categories/' \ +-H 'Authorization: Bearer ' +``` + + + + +This request requires the ID of the category to be passed as a path parameter. + +The request returns the following fields: + +- `id`: The ID of the deleted category. +- `object`: The type of object that was deleted. In this case, the value will be `product-category`. +- `deleted`: A boolean value indicating whether the category was deleted. \ No newline at end of file diff --git a/docs/content/modules/products/categories.md b/docs/content/modules/products/categories.md new file mode 100644 index 0000000000..5b0d97dc88 --- /dev/null +++ b/docs/content/modules/products/categories.md @@ -0,0 +1,94 @@ +--- +description: 'Learn what product categories are and how they work in a Medusa backend. Product categories can be used to organize products using nested collections.' +--- + +# Product Categories + +In this document, you’ll learn about Product Categories and how they can be used in Medusa. + +:::note + +Product Category feature is currently in beta mode and guarded by a feature flag. To use Product Categories either: + +1. Enable the `MEDUSA_FF_PRODUCT_CATEGORIES` environment variable; +2. Or enable the `product_categories` key in the Medusa backend's settings. + +You can learn more about enabling it in the [feature flags](../../development/feature-flags/toggle.md) documentation. + +::: + +## Introduction + +Product Categories allow you to categorize your products. You can manage categories in your store. You can also nest categories in one another, creating a parent-child hierarchy, and there is no limit to how far you can nest the categories. + +In addition, you can give a category a ranking. As a product can have multiple categories, rankings can be useful in different cases. For example, when you’re unsure what main category to show in the product’s page in the frontend. + +### Example Use Cases + +Here are some examples of the usage of Product Categories: + +- Organizing your large catalog store in a way that showcases to the customer what products you provide. +- Create a multi-brand store, where each category acts as a brand. + +--- + +## Product Category vs Product Collection + +Before v1.8 of Medusa, Product Collection was used as the main way of organizing products. It was used similarly to how you would use a Product Category. However, a Product Collection is not only different in what it actually represents, it also doesn’t provide the same functionalities that a Product Category does. + +A Product Collection is used to group together a list of products, generally for a marketing purpose. For example, you can create a summer collection when summer comes around with a variety of products in them including shorts, t-shirts, and more. + +On the other hand, a Product Category is used to provide easy and clear navigation for customers to help them find what they need. For example, you can have the following nested product categories: + +- Women + - Tops + - Jackets + - Sweatshirt + - T-shirt + - etc… + - Bottoms + - Jeans + - Shorts + - etc… + +This allows customers to directly choose the category that the product they’re looking for belongs to, and gives them an idea of what products your store offers. If you were to use Product Collections here, as they don’t support nesting, all of these categories would have to be separate collections that are not tied together. + +--- + +## ProductCategory Entity Overview + +A product category is stored in the database as a `ProductCategory` entity. Some of its important attributes are: + +- `id`: The ID of the product category. +- `name`: The name of the product category. +- `handle`: A string indicating a slug path of the category. It’s useful when creating a page on your storefront for the category, as the `handle` can be used as the path in the URL. +- `is_active`: A boolean value indicating the status of the Product Category. +- `is_internal`: A boolean value indicating the visibility of the Product Category. +- `mpath`: A string value that keeps the IDs of all the category’s ancestors, delimited by a dot (`.`). This attribute is generated by Typeorm. For example, `pcat_id1.pcat_id2.pcat_id3`. + +--- + +## Relations to other Entities + +### Products + +Products can be available in more than one category. You can then filter products by a single or list of product categories. + +The relation is implemented in the Product entity. You can access the product categories a product is available in by expanding the `categories` relation and using `product.categories` + +### ProductCategory + +The parent-child hierarchy is represented in the `ProductCategory` entity through the following relations: + +- You can access the parent of a category by expanding the `parent_category` relation and accessing `category.parent_category`. You can also access the ID of the parent category by accessing `category.parent_category_id`. If a category doesn’t have a parent, the relation and the ID will be `null`. +- You can access the children of a category by expanding the `category_children` relation and accessing `category.category_children`. If a category doesn’t have children, the relation will be an empty array. By default, when expanding this relation in Product Category endpoints, only the immediate child categories are returned. If you want to get the entire heirarchy of child categories, you must pass the `include_descendants_tree` flag to the endpoint setting its value to `true`. + +Aside from these relations, the `mpath` attribute, which is a [Materialized Path](https://typeorm.io/tree-entities#materialized-path-aka-path-enumeration) that is automatically generated by Typeorm, can be used to query deep trees faster. + +![product-categories.jpg](https://res.cloudinary.com/dza7lstvk/image/upload/v1679916789/Medusa%20Docs/Diagrams/product-categories_x4qp5u.jpg) + +--- + +## See Also + +- [How to manage product categories using the admin APIs](./admin/manage-categories.mdx) \ No newline at end of file diff --git a/docs/content/modules/products/overview.mdx b/docs/content/modules/products/overview.mdx index 72ddbbff7f..8243a39358 100644 --- a/docs/content/modules/products/overview.mdx +++ b/docs/content/modules/products/overview.mdx @@ -29,7 +29,6 @@ Admins can manage unlimited amount of products and their variants. They can mana customProps: { icon: Icons['academic-cap-solid'], description: 'Learn how to manage products using Admin APIs.', - isSoon: true, } }, { @@ -55,37 +54,37 @@ Admins can manage unlimited amount of products and their variants. They can mana ### Product Organization -Admins can organize products into collections, tags, types, and more. +Admins can organize products into categories, collections, tags, types, and more. Customers can use this organization to filter products while browsing them. /admin/regions`, { credentials: "include", method: "POST", + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ name: "Europe", currency_code: "eur", @@ -251,7 +254,7 @@ This request requires the following body parameters: - `name`: The name of the region. - `currency_code`: The 3 character ISO currency code. - `tax_rate`: The tax rate in the Region. -- `payment_providers`: An array of payment provider IDs. The array must contain at least one item. +- `payment_providers`: An array of payment processor IDs. The array must contain at least one item. - `fulfillment_providers`: An array of fulfillment provider IDs. The array must contain at least one item. - `countries`: An array of the 2 character ISO code of the countries included in the region. @@ -314,6 +317,9 @@ export default UpdateRegion fetch(`/admin/regions/${regionId}`, { credentials: "include", method: "POST", + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ countries: [ "DK", @@ -416,6 +422,9 @@ export default Region fetch(`/admin/shipping-options`, { credentials: "include", method: "POST", + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ name: "PostFake", region_id: regionId, diff --git a/docs/content/modules/regions-and-currencies/overview.mdx b/docs/content/modules/regions-and-currencies/overview.mdx index 56b1f740bb..fd01c2809c 100644 --- a/docs/content/modules/regions-and-currencies/overview.mdx +++ b/docs/content/modules/regions-and-currencies/overview.mdx @@ -23,7 +23,7 @@ In Medusa, you can create an unlimited number of regions. A region can be associ For example, you can create a region to represent the European region, and associate European countries with that region. You can also create a region that represents each of these countries on their own. -Each region can have its own settings such as the currency, payment providers, tax providers, and more. +Each region can have its own settings such as the currency, payment processors, tax providers, and more. { const paypalSession = useMemo(() => { if (cart.payment_sessions) { return cart.payment_sessions.find( - (s) => s.provider_id === "paypal" + (s) => s.processor_id === "paypal" ) } @@ -245,7 +245,7 @@ const PaypalPayment = () => { } return ( - { onApprove={handlePayment} disabled={processing} /> - + ) } @@ -269,18 +269,18 @@ export default PaypalPayment Here’s briefly what this code snippet does: -1. This component renders a PayPal button to initialize the payment using PayPal. You use the components from the PayPal React components library to render the button and you pass the `PayPalScriptProvider` component the Client ID. +1. This component renders a PayPal button to initialize the payment using PayPal. You use the components from the PayPal React components library to render the button and you pass the `PayPalScriptProcessor` component the Client ID. 2. When the button is clicked, the `handlePayment` function is executed. In this method, you initialize the payment authorization using `actions.order.authorize()`. It takes the customer to another page to log in with PayPal and authorize the payment. 3. After the payment is authorized successfully on PayPal’s portal, the fulfillment function passed to `actions.order.authorize().then` will be executed which calls the `completeOrder` function. -4. In `completeOrder`, you first ensure that the payment session for the PayPal payment provider is set as the [selected Payment Session in the cart](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). Then, you send a request to the backend to [update the payment session](/api/store#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) data with the authorization data received from PayPal. +4. In `completeOrder`, you first ensure that the payment session for the PayPal payment processor is set as the [selected Payment Session in the cart](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). Then, you send a request to the backend to [update the payment session](/api/store#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) data with the authorization data received from PayPal. 5. You then [complete the cart and place the order](/api/store/#tag/Cart/operation/PostCartsCartComplete). If that is done successfully, you navigate to the `/order-confirmed` page. -The last step is to add this component as the component to render when PayPal is available as a payment provider. +The last step is to add this component as the component to render when PayPal is available as a payment processor. -In `src/components/payment/index.js` you’ll find in the return statement a switch statement that checks the payment provider for each payment session and renders the component based on the ID. Add before the `default` case a case for `paypal`: +In `src/components/payment/index.js` you’ll find in the return statement a switch statement that checks the payment processor for each payment session and renders the component based on the ID. Add before the `default` case a case for `paypal`: ```jsx title=src/components/payment/index.js -switch (ps.provider_id) { +switch (ps.processor_id) { case "stripe": // ... break @@ -325,7 +325,7 @@ Next, create the file that will hold the PayPal component with the following con ```jsx import { PayPalButtons, - PayPalScriptProvider, + PayPalScriptProcessor, } from "@paypal/react-paypal-js" import { useEffect, useState } from "react" @@ -350,7 +350,7 @@ function Paypal() { const response = await client .carts .setPaymentSession(cart.id, { - "provider_id": "paypal", + "processor_id": "paypal", }) if (!response.cart) { @@ -383,7 +383,7 @@ function Paypal() { return (
    {cart !== undefined && ( - ", "currency": "EUR", "intent": "authorize", @@ -398,7 +398,7 @@ function Paypal() { onApprove={handlePayment} disabled={processing} /> - + )}
    ) @@ -411,10 +411,10 @@ Here’s briefly what this code snippet does: 1. At the beginning of the component, the Medusa client is initialized using the JS Client you installed. 2. You also need to retrieve the cart. Ideally, the cart should be managed through a context. So, every time the cart has been updated the cart should be updated in the context to be accessed from all components. -3. This component renders a PayPal button to initialize the payment using PayPal. You use the components from the PayPal React components library to render the button and you pass the `PayPalScriptProvider` component the Client ID. Make sure to replace `` with the environment variable you added. +3. This component renders a PayPal button to initialize the payment using PayPal. You use the components from the PayPal React components library to render the button and you pass the `PayPalScriptProcessor` component the Client ID. Make sure to replace `` with the environment variable you added. 4. When the button is clicked, the `handlePayment` function is executed. In this method, you initialize the payment authorization using `actions.order.authorize()`. It takes the customer to another page to log in with PayPal and authorize the payment. 5. After the payment is authorized successfully on PayPal’s portal, the fulfillment function passed to `actions.order.authorize().then` will be executed. -6. In the fulfillment function, you first ensure that the payment session for the PayPal payment provider is set as the [selected Payment Session in the cart](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). Then, you send a request to the backend to [update the payment session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) data with the authorization data received from PayPal. +6. In the fulfillment function, you first ensure that the payment session for the PayPal payment processor is set as the [selected Payment Session in the cart](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession). Then, you send a request to the backend to [update the payment session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) data with the authorization data received from PayPal. 7. You then [complete the cart and place the order](/api/store/#tag/Cart/operation/PostCartsCartComplete). If that is done successfully, you just show a success alert. You can change this based on the behavior you want in your storefront. You can then import this component where you want to show it in your storefront. diff --git a/docs/content/plugins/payment/stripe.md b/docs/content/plugins/payment/stripe.md index 48338f4102..72fae29c1b 100644 --- a/docs/content/plugins/payment/stripe.md +++ b/docs/content/plugins/payment/stripe.md @@ -21,7 +21,7 @@ You can also follow this video guide to learn how the setup works: [Stripe](https://stripe.com/) is a battle-tested and unified platform for transaction handling. Stripe supplies you with the technical components needed to handle transactions safely and all the analytical features necessary to gain insight into your sales. These features are also available in a safe test environment which allows for a concern-free development process. -Using the `medusa-payment-stripe` plugin, this guide shows you how to set up your Medusa project with Stripe as a payment provider. +Using the `medusa-payment-stripe` plugin, this guide shows you how to set up your Medusa project with Stripe as a payment processor. --- @@ -33,7 +33,7 @@ Before you proceed with this guide, make sure you create a [Stripe account](http ## Medusa Backend -This section guides you over the steps necessary to add Stripe as a payment provider to your Medusa backend. +This section guides you over the steps necessary to add Stripe as a payment processor to your Medusa backend. If you don’t have a Medusa backend installed yet, you must follow the [quickstart guide](../../development/backend/install.mdx) first. @@ -86,7 +86,7 @@ STRIPE_API_KEY=sk_... :::note -If you store environment variables differently on your backend, for example, using the hosting provider’s UI, then you don’t need to add it in `.env`. Add the environment variables in a way relevant to your backend. +If you store environment variables differently on your backend, for example, using the hosting processor’s UI, then you don’t need to add it in `.env`. Add the environment variables in a way relevant to your backend. ::: @@ -108,9 +108,9 @@ STRIPE_WEBHOOK_SECRET=whsec_... ## Admin Setup -This section will guide you through adding Stripe as a payment provider in a region using your Medusa admin dashboard. +This section will guide you through adding Stripe as a payment processor in a region using your Medusa admin dashboard. -This step is required for you to be able to use Stripe as a payment provider in your storefront. +This step is required for you to be able to use Stripe as a payment processor in your storefront. ### Admin Prerequisites @@ -118,7 +118,7 @@ If you don’t have a Medusa admin installed, make sure to follow along with [th ### Add Stripe to Regions -You can refer to [this documentation in the user guide](../../user-guide/regions/providers.mdx#manage-payment-providers) to learn how to add a payment provider like Stripe to a region. +You can refer to [this documentation in the user guide](../../user-guide/regions/providers.mdx#manage-payment-providers) to learn how to add a payment processor like Stripe to a region. --- @@ -178,8 +178,8 @@ This section will go over how to add Stripe into a React-based framework. The in The integration with stripe must have the following workflow: -1. During checkout when the user reaches the payment section, you should [create payment sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions). This will initialize the `payment_sessions` array in the `cart` object received. The `payment_sessions` is an array of available payment providers. -2. If Stripe is available as a payment provider, you should select Stripe as [the payment session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) for the current cart. This will initialize the `payment_session` object in the `cart` object to include data related to Stripe and the current payment session. This includes the payment intent and client secret. +1. During checkout when the user reaches the payment section, you should [create payment sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions). This will initialize the `payment_sessions` array in the `cart` object received. The `payment_sessions` is an array of available payment processors. +2. If Stripe is available as a payment processor, you should select Stripe as [the payment session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) for the current cart. This will initialize the `payment_session` object in the `cart` object to include data related to Stripe and the current payment session. This includes the payment intent and client secret. 3. After the user enters their card details and submits the form, confirm the payment with Stripe. 4. If the payment is confirmed successfully, [complete the order](/api/store/#tag/Cart/operation/PostCartsCartComplete) in Medusa. Otherwise show an error. @@ -299,7 +299,7 @@ client.carts.createPaymentSessions(cart.id) // check if stripe is selected const isStripeAvailable = cart.payment_sessions?.some( (session) => ( - session.provider_id === "stripe" + session.processor_id === "stripe" ) ) if (!isStripeAvailable) { @@ -308,7 +308,7 @@ client.carts.createPaymentSessions(cart.id) // select stripe payment session client.carts.setPaymentSession(cart.id, { - provider_id: "stripe", + processor_id: "stripe", }).then(({ cart }) => { setClientSecret(cart.payment_session.data.client_secret) }) diff --git a/docs/content/plugins/search/algolia.md b/docs/content/plugins/search/algolia.md index 9ac63a3ea5..001a5f9a32 100644 --- a/docs/content/plugins/search/algolia.md +++ b/docs/content/plugins/search/algolia.md @@ -23,15 +23,7 @@ Through Medusa's flexible plugin system, it is possible to add a search engine t ### Medusa Components -It is required to have a Medusa backend installed before starting with this documentation. If not, please follow along with the [quickstart guide](../../development/backend/install.mdx) to get started in minutes. - -Furthermore, it’s highly recommended to ensure your Medusa backend is configured to work with Redis. As Medusa uses Redis for the event queue internally, configuring Redis ensures that the search indices in Algolia are updated whenever products on the Medusa backend are updated. You can follow [this documentation to install Redis](../../development/backend/prepare-environment.mdx#redis) and then [configure it on your Medusa backend](../../development/backend/configurations.md#redis). - -:::caution - -If you don’t install and configure Redis on your Medusa backend, the Algolia integration will still work. However, products indexed in Algolia are only added and updated when you restart the Medusa backend. - -::: +It is required to have a Medusa backend installed before starting with this documentation. If not, please follow along with the [quickstart guide](../../development/backend/install.mdx) to get started in minutes. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### Algolia Account @@ -100,38 +92,100 @@ Where `` and `` are respectively the Applicatio Finally, in `medusa-config.js` add the following item into the `plugins` array: -```jsx title=medusa-config.js +```js title=medusa-config.js const plugins = [ // ... { resolve: `medusa-plugin-algolia`, options: { - application_id: process.env.ALGOLIA_APP_ID, - admin_api_key: process.env.ALGOLIA_ADMIN_API_KEY, + applicationId: process.env.ALGOLIA_APP_ID, + adminApiKey: process.env.ALGOLIA_ADMIN_API_KEY, settings: { - products: { - searchableAttributes: ["title", "description"], - attributesToRetrieve: [ - "id", - "title", - "description", - "handle", - "thumbnail", - "variants", - "variant_sku", - "options", - "collection_title", - "collection_handle", - "images", - ], - }, + // index settings... }, }, }, ] ``` -The `searchableAttributes` are the attributes in a product that are searchable, and `attributesToRetrieve` are the attributes to retrieve for each product result. You’re free to make changes to these attributes as you see fit, but these are the recommended attributes. +### Index Settings + +Under the `settings` key of the plugin's options, you can add settings specific to each index. The settings are of the following format: + +```js +const plugins = [ + // ... + { + resolve: `medusa-plugin-algolia`, + options: { + // other options... + settings: { + indexName: { + indexSettings: { + searchableAttributes, + attributesToRetrieve, + }, + transformer, + }, + }, + }, + }, +] +``` + +Where: + +- `indexName`: the name of the index to create in Algolia. For example, `products`. Its value is an object containing the following properties: + - `indexSettings`: an object that includes the following properties: + - `searchableAttributes`: an array of strings indicating the attributes in the product entity that can be searched. + - `attributesToRetrieve`: an array of strings indicating the attributes in the product entity that should be retrieved in the search results. + - `transformer`: an optional function that accepts a product as a parameter and returns an object to be indexed. This allows you to have more control over what you're indexing. For example, you can add details related to variants or custom relations, or you can filter out certain products. + +Using this index settings structure, you can add more than one index. + +:::tip + +These settings are just examples of what you can pass to the Algolia provider. If you need to pass more settings to the Algolia SDK you can pass it inside `indexSettings`. + +::: + +Here's an example of the settings you can use: + +```js title=medusa-config.js +const plugins = [ + // ... + { + resolve: `medusa-plugin-algolia`, + options: { + // other options... + settings: { + products: { + indexSettings: { + searchableAttributes: ["title", "description"], + attributesToRetrieve: [ + "id", + "title", + "description", + "handle", + "thumbnail", + "variants", + "variant_sku", + "options", + "collection_title", + "collection_handle", + "images", + ], + }, + transform: (product) => ({ + id: product.id, + // other attributes... + }), + }, + }, + }, + }, +] +``` --- @@ -163,7 +217,7 @@ If you add or update products on your Medusa backend, the addition or update wil :::note -This feature is only available if you have Redis installed and configured with your Medusa backend as mentioned in the [Prerequisites section](#prerequisites). Otherwise, you must re-run the Medusa backend to see the change in the Algolia indices. +This feature is only available if you have an event module installed in your Medusa backend, as explained in the Prerequisites section. ::: diff --git a/docs/content/plugins/search/meilisearch.md b/docs/content/plugins/search/meilisearch.md index 1362ee4d2d..6aa0b8f426 100644 --- a/docs/content/plugins/search/meilisearch.md +++ b/docs/content/plugins/search/meilisearch.md @@ -21,15 +21,7 @@ Through Medusa's flexible plugin system, it is possible to add a search engine t ### Medusa Components -It is required to have a Medusa backend installed before starting with this documentation. If not, please follow along with the [quickstart guide](../../development/backend/install.mdx) to get started in minutes. - -Furthermore, it’s highly recommended to ensure your Medusa backend is configured to work with Redis. As Medusa uses Redis for the event queue internally, configuring Redis ensures that the search indices in MeiliSearch are updated whenever products on the Medusa backend are updated. You can follow [this documentation to install Redis](../../development/backend/prepare-environment.mdx#redis) and then [configure it on your Medusa backend](../../development/backend/configurations.md#redis). - -:::caution - -If you don’t install and configure Redis on your Medusa backend, the MeiliSearch integration will still work. However, products indexed in MeiliSearch are only added and updated when you restart the Medusa backend. - -::: +It is required to have a Medusa backend installed before starting with this documentation. If not, please follow along with the [quickstart guide](../../development/backend/install.mdx) to get started in minutes. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter. ### MeiliSearch Instance @@ -73,30 +65,92 @@ const plugins = [ apiKey: process.env.MEILISEARCH_API_KEY, }, settings: { - // index name - products: { - // MeiliSearch's setting options - // to be set on a particular index - searchableAttributes: [ - "title", - "description", - "variant_sku", - ], - displayedAttributes: [ - "title", - "description", - "variant_sku", - "thumbnail", - "handle", - ], - }, + // index settings... }, }, }, ] ``` -You can change the `searchableAttributes` and `displayedAttributes` as you see fit. However, the attributes included are the recommended attributes. +### Index Settings + +Under the `settings` key of the plugin's options, you can add settings specific to each index. The settings are of the following format: + +```js +const plugins = [ + // ... + { + resolve: `medusa-plugin-meilisearch`, + options: { + // other options... + settings: { + indexName: { + indexSettings: { + searchableAttributes, + displayedAttributes, + }, + primaryKey, + transformer, + }, + }, + }, + }, +] +``` + +Where: + +- `indexName`: the name of the index to create in MeiliSearch. For example, `products`. Its value is an object containing the following properties: + - `indexSettings`: an object that includes the following properties: + - `searchableAttributes`: an array of strings indicating the attributes in the product entity that can be searched. + - `displayedAttributes`: an array of strings indicating the attributes in the product entity that should be displayed in the search results. + - `primaryKey`: an optional string indicating which property acts as a primary key of a document. It's used to enforce unique documents in an index. The default value is `id`. You can learn more in [MeiliSearch's documentation](https://docs.meilisearch.com/learn/core_concepts/primary_key.html#primary-field). + - `transformer`: an optional function that accepts a product as a parameter and returns an object to be indexed. This allows you to have more control over what you're indexing. For example, you can add details related to variants or custom relations, or you can filter out certain products. + +Using this index settings structure, you can add more than one index. + +:::tip + +These settings are just examples of what you can pass to the MeiliSearch provider. If you need to pass more settings to the MeiliSearch SDK you can pass it inside `indexSettings`. + +::: + +Here's an example of the settings you can use: + +```js title=medusa-config.js +const plugins = [ + // ... + { + resolve: `medusa-plugin-meilisearch`, + options: { + // other options... + settings: { + products: { + indexSettings: { + searchableAttributes: [ + "title", + "description", + "variant_sku", + ], + displayedAttributes: [ + "title", + "description", + "variant_sku", + "thumbnail", + "handle", + ], + }, + primaryKey: "id", + transform: (product) => ({ + id: product.id, + // other attributes... + }), + }, + }, + }, + }, +] +``` --- @@ -124,7 +178,7 @@ If you add or update products on your Medusa backend, the addition or update wil :::note -This feature is only available if you have Redis installed and configured with your Medusa backend as mentioned in the [Prerequisites section](#prerequisites). Otherwise, you must re-run the Medusa backend to see the change in the MeiliSearch indices. +This feature is only available if you have an event module installed in your Medusa backend, as explained in the Prerequisites section. ::: diff --git a/docs/content/starters/nextjs-medusa-starter.mdx b/docs/content/starters/nextjs-medusa-starter.mdx index 5aa9f57273..4a1ec8f82e 100644 --- a/docs/content/starters/nextjs-medusa-starter.mdx +++ b/docs/content/starters/nextjs-medusa-starter.mdx @@ -270,4 +270,4 @@ You can learn more about development with Next.js through [their documentation]( - [Storefront API reference](https://docs.medusajs.com/api/store) - [Install Medusa Admin](../admin/quickstart.mdx). -- [Install Stripe as a payment provider](../plugins/payment/stripe.md#add-to-nextjs-storefront) +- [Install Stripe as a payment processor](../plugins/payment/stripe.md#add-to-nextjs-storefront) diff --git a/docs/content/troubleshooting/awilix-resolution-error.md b/docs/content/troubleshooting/awilix-resolution-error.md new file mode 100644 index 0000000000..561a3825c5 --- /dev/null +++ b/docs/content/troubleshooting/awilix-resolution-error.md @@ -0,0 +1,78 @@ +# AwilixResolutionError: Could Not Resolve X + +This troubleshooting guide will help you figure out the different situations that can cause an `AwilixResolutionError`. + +## Option 1: Service Lifetime + +If you're registering a custom resource within a middleware, for example a logged-in user, then make sure that all services that are using it have their `LIFE_TIME` static property either set to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. This mainly applies for services in the core Medusa package, as, by default, their lifetime is `Lifetime.SINGLETON`. + +For example: + +```ts +import { Lifetime } from "awilix" +import { + ProductService as MedusaProductService, +} from "@medusajs/medusa" + +// extending ProductService from the core +class ProductService extends MedusaProductService { + // The default life time for a core service is SINGLETON + static LIFE_TIME = Lifetime.SCOPED + + // ... +} + +export default ProductService +``` + +This may require you to extend a service as explained in [this documentation](../development/services/extend-service.md) if necessary. + +If you're unsure which service you need to change its `LIFE_TIME` property, it should be mentioned along with the `AwilixResolutionError` message. For example: + +```bash noCopy noReport +AwilixResolutionError: Could not resolve 'loggedInUser'. + +Resolution path: cartService -> productService -> loggedInUser +``` + +As shown in the resolution path, you must change the `LIFE_TIME` property of both `cartService` and `productService` to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. + +You can learn about the service lifetime in the [Create a Service documentation](../development/services/create-service.md). + +## Option 2: Using Try-Catch Block with Custom Registration + +When you register a custom resource using a middleware, make sure that when you use it in a service's constructor you wrap it in a try-catch block. This can cause an error when the Medusa backend first runs, especially if the service is used within a subscriber. Subscribers are built the first time the Medusa backend runs, meaning that their dependencies are registered at that point. Since your custom resource hasn't been registered at this point, it will cause an `AwilixResolutionError` when the backend tries to resolve it. + +For that reason, and to avoid other similar situations, make sure to always wrap your custom resources in a try-catch block when you use them inside the constructor of a service. For example: + + + +```ts +import { TransactionBaseService } from "@medusajs/medusa" + +class CustomService extends TransactionBaseService { + + constructor(container, options) { + super(...arguments) + + // use the registered resource. + try { + container.customResource + } catch (e) { + // avoid errors when the backend first loads + } + } +} + +export default CustomService +``` + +You can learn more about this in the [Middlewares documentation](../development/endpoints/add-middleware.md). + +## Option 3: Error on A Fresh Installation + +If you get the error on a fresh installation of the Medusa backend, or you haven't made any customizations that would cause this error, try to remove the `node_modules` directory, then run the following command in the root directory of the Medusa backend to re-install the dependencies: + +```bash npm2yarn +npm install +``` \ No newline at end of file diff --git a/docs/content/troubleshooting/missing-payment-providers.md b/docs/content/troubleshooting/missing-payment-providers.md index 772109c099..bf1e33764f 100644 --- a/docs/content/troubleshooting/missing-payment-providers.md +++ b/docs/content/troubleshooting/missing-payment-providers.md @@ -1,6 +1,6 @@ -# Payment Provider (Stripe) not showing in checkout +# Payment Processor (Stripe) not showing in checkout -You add payment providers to your Medusa instance by adding them as plugins in `medusa-config.js`: +You add payment processors to your Medusa instance by adding them as plugins in `medusa-config.js`: ```js title=medusa-config.js const plugins = [ @@ -24,7 +24,7 @@ npm install medusa-payment-stripe However, to also show them as part of your checkout flow you need to add them to your regions. -In the Medusa Admin go to Settings > Regions and for each region scroll down to the Payment Provider input and choose the payment provider you want to use in that region. +Then, refer to [this user guide](../user-guide/regions/providers.mdx) to learn how to enable the payment processor in a region. --- diff --git a/docs/content/troubleshooting/redis-events.md b/docs/content/troubleshooting/redis-events.md index d0e7d1f338..9dfca49ff1 100644 --- a/docs/content/troubleshooting/redis-events.md +++ b/docs/content/troubleshooting/redis-events.md @@ -1,5 +1,11 @@ # Redis not emitting events +:::note + +This troubleshooting guide only applies to Medusa backends using versions before v1.8 of the core Medusa package. + +::: + When you create a new Medusa backend, Redis is disabled by default. Instead, a fake Redis backend is used that allows you to start your project but does not actually emit any events. To enable a real Redis backend, you need to install Redis on your machine and configure it with Medusa. diff --git a/docs/content/upgrade-guides/admin/1-0-0.md b/docs/content/upgrade-guides/admin/1-0-0.md new file mode 100644 index 0000000000..11147cf25d --- /dev/null +++ b/docs/content/upgrade-guides/admin/1-0-0.md @@ -0,0 +1,48 @@ +--- +description: "Migrate from the GitHub repository to the NPM package." +--- + +# Medusa Admin: 1.0.0 + +Medusa Admin has been moved from being hosted on a GitHub repository to being published as an NPM package that can be installed in the same project as the Medusa backend. This upgrade guide will help you find the right resources to make the upgrade. + +:::note + +If you made customizations to the admin and you want to preserve these customization, then it's recommended not to update to use the new admin plugin. You can learn more in [this section](#preserving-customizations-in-the-admin). + +::: + +## Overview + +The Admin is now composed of two packages `@medusajs/admin` and `@medusajs/admin-ui`. The `@medusajs/admin` package is the plugin that can be installed in the Medusa server project. The `@medusajs/admin-ui` package is the UI that is served by the plugin. + +This guide will cover three cases where the move might affect your current setup: + +- Updating your existing project to use the plugin +- Replacing your already deployed admin with the plugin +- Preserving customizations in the plugin + +## Actions required + +### Updating your existing project to use the plugin + +As mentioned, Medusa Admin is now distributed via NPM and installed as a plugins. To replace your existing admin, you first need to install the plugin in your Medusa server project. + +Follow the [admin quickstart guide](../../admin/quickstart.mdx) to learn how to set up the plugin with the Medusa backend. + +### Deploy the New Admin Plugin + +The move to an NPM package has implications for the workflow to deploy the Admin. The plugin offers to ways to deploy the admin, either by serving the Admin directly from the Medusa server, or by deploying the Admin to a separate hosting platform. + +To host the Admin directly from your Medusa server, you can follow the [Admin Quickstart Guide](../../admin/quickstart.mdx). + +You can learn how to deploy the Admin to a host through the [Vercel Deployment Guide](../../deployments/admin/deploying-on-vercel.md). The process is similar for other Git based hosting platforms. + +### Preserving Customizations in the Admin + +If you have made customizations to the Admin, it is recommend that you keep your current setup and wait for the next minor release of Medusa Admin, that will introduce a more seamless way to extend the UI. Migrating customizations to the new admin plugin is not supported, and will require manaully patching any changes or forking the Admin plugin. The current Admin repository will still receive critical bug fixes while in maintenance, but it will not be updated with new features. If you can forego the latest features introduced in 1.8 you can continue to use the standalone admin repository for now. + +While it's generally not recommended, if you want to maintain your customizations while upgrading to the latest version of Medusa Admin, you have two options: + +1. Fork the @medusajs/admin plugin and @medusajs/admin-ui, and manually patch in your customizations. +2. Use the medusa-admin eject -o command to eject the Admin UI from the plugin and use it as a separate project. This way, you can make your customizations and deploy them to a separate hosting platform. diff --git a/docs/content/upgrade-guides/index.mdx b/docs/content/upgrade-guides/index.mdx index 27e9496934..e98e2f2cf1 100644 --- a/docs/content/upgrade-guides/index.mdx +++ b/docs/content/upgrade-guides/index.mdx @@ -22,8 +22,8 @@ Find in this page the upgrade guides that require necessary steps when upgrading item={getFirstCategoryItem('Medusa React')} /> -## Admin +## Admin Dashboard \ No newline at end of file diff --git a/docs/content/upgrade-guides/medusa-core/1-8-0.md b/docs/content/upgrade-guides/medusa-core/1-8-0.md new file mode 100644 index 0000000000..a2347d4268 --- /dev/null +++ b/docs/content/upgrade-guides/medusa-core/1-8-0.md @@ -0,0 +1,89 @@ +--- +description: 'Actions required for v1.8' +sidebar_custom_props: + iconName: 'server-stack-solid' +--- + +# v1.8 + +Medusa v1.8 comes with many new features while introducing architectural changes contributing toward making Medusa more modular and portable to new, modern environments. + +This has led to breaking changes and this document will guide you through the required actions. + +Please note that by upgrading to v1.8 of Medusa, you must also upgrade your admin to become a plugin as explained in the [admin upgrade guide](../admin/1-0-0.md). If you've made customizations to your admin, please refer to [this section of the admin upgrade guide](../admin/1-0-0.md#preserving-customizations-in-the-admin) before considering updating to v1.8 of Medusa. + +## Required Actions + +:::note + +It's recommended to use yarn when updating the following dependency to avoid any unexpected errors. + +::: + +### Step 1: Update Typeorm + +To get started using Medusa v1.8, you first need to upgrade your version of Typeorm: + +```bash +yarn add typeorm@0.3.11 +``` + +The dependency on Typeorm has been upgraded from 0.2.31 to 0.3.11, which comes with significant breaking changes. Follow Typeorm's upgrade guide to refactor your custom code. + +### Step 2: Update Core Package + +Install version 1.8 of the core: + +```bash +yarn add @medusajs/medusa@1.8.0 +``` + +### Step 3: Install Required Modules + +The core engine doesn't come with a Redis caching mechanism and Redis events system any longer. Instead the core relies on the Module API for those two sub-systems. + +As a result, you are required to install and use those modules to ensure your application works as expected. + +Install the new Redis cache module with the following command: + +```bash +yarn add @medusajs/cache-redis@1.8.0 +``` + +Install the new Redis event bus module with the following command: + +```bash +yarn add @medusajs/event-bus-redis@1.8.0 +``` + +Then, add both modules to the exported configuration in `medusa-config.js`. + +```js title=medusa-config.js +module.exports = { + // ... + modules: { + eventBus: { + resolve: "@medusajs/event-bus-redis", + options: { + redisUrl: "your-redis-url", + }, + }, + cacheService: { + resolve: "@medusajs/cache-redis", + options: { + redisUrl: "your-redis-url", + }, + }, + }, +} +``` + +Make sure to replace `your-redis-url` with the connection URL to your Redis installation. + +### Step 4: Run Migrations + +Finally, you should run migrations to ensure your database is up to date with our schema changes: + +```bash +medusa migrations run +``` diff --git a/docs/content/upgrade-guides/plugins/_category_.json b/docs/content/upgrade-guides/plugins/_category_.json new file mode 100644 index 0000000000..d4477d2f20 --- /dev/null +++ b/docs/content/upgrade-guides/plugins/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 3, + "link": null, + "label": "Plugins" +} \ No newline at end of file diff --git a/docs/content/upgrade-guides/plugins/algolia/1-0-0.md b/docs/content/upgrade-guides/plugins/algolia/1-0-0.md new file mode 100644 index 0000000000..e4bb950796 --- /dev/null +++ b/docs/content/upgrade-guides/plugins/algolia/1-0-0.md @@ -0,0 +1,71 @@ +--- +description: 'Actions Required for v.1.0.0' +sidebar_label: 'v1.0.0' +--- + +# Algolia: v1.0.0 + +Version 1.0.0 of the official Algolia plugin comes with breaking changes to the plugin options that are passed to the Algolia service through `medusa-config.js`. + +## Overview + +In the new version of the Algolia search plugin, two new plugin configuration properties are introduced; `transformer` and `primaryKey`. As a result, the way indexes in Algolia are configured has changed. Additionally, the existing settings have been changed to follow a camel casing - though with backward compatibility. + +--- + +## How to Update + +Run the following command in the root directory of your Medusa Backend: + +```bash npm2yarn +npm install medusa-plugin-algolia@1.0.0 +``` + +--- + +## Actions required + +As you can see from the new object shape, the property `indexSettings` has been introduced to hold the settings specific to Algolia’s index options. This has been done to make space for the `transformer`. + +Previously, you might have configured the Algolia plugin as seen below: + +```js title=medusa-config.js +const plugins = [ + // ... + { + application_id: "someId", + admin_api_key: "someApiKey", + settings: { + // example + products: { + searchableAttributes: ["title", "description"], + attributesToRetrieve: ["title", "description"], + }, + }, + }, +] +``` + +In the above example, an index `products` has been configured with two options `searchableAttributes` and `attributesToRetrieve`. Updating to v1.0.0 requires you to nest these within the `indexSettings`. Additionally, the admin API key and application ID options should now be in camel case, as the snake-cased version will be deprecated. + +The updated plugin options would look like so: + +```js title=medusa-config.js +const plugins = [ + // ... + { + applicationId: "someId", + adminApiKey: "someApiKey", + settings: { + products: { + indexSettings: { + searchableAttributes: ["title", "description"], + attributesToRetrieve: ["title", "description"], + }, + }, + }, + }, +] +``` + +You can learn more about the new plugin options in the [Algolia plugin documentation](../../../plugins/search/algolia.md). \ No newline at end of file diff --git a/docs/content/upgrade-guides/plugins/algolia/_category_.json b/docs/content/upgrade-guides/plugins/algolia/_category_.json new file mode 100644 index 0000000000..ede3e1e84b --- /dev/null +++ b/docs/content/upgrade-guides/plugins/algolia/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Algolia", + "customProps": { + "reverse": true + } +} \ No newline at end of file diff --git a/docs/content/upgrade-guides/plugins/meilisearch/1-0-0.md b/docs/content/upgrade-guides/plugins/meilisearch/1-0-0.md new file mode 100644 index 0000000000..bfe57903cc --- /dev/null +++ b/docs/content/upgrade-guides/plugins/meilisearch/1-0-0.md @@ -0,0 +1,74 @@ +--- +description: 'Actions Required for v.1.0.0' +sidebar_label: 'v1.0.0' +--- + +# Meilisearch: v1.0.0 + +Version 1.0.0 of the official Meilisearch plugin comes with breaking changes to the plugin options that are passed to the Meilisearch service through `medusa-config.js`. + +## Overview + +In the new version of the Meilisearch search plugin, two new plugin configuration properties are introduced; `transformer` and `primaryKey`. As a result, the way indexes in Meilisearch are configured has changed. + +--- + +## How to Update + +Run the following command in the root directory of your Medusa Backend: + +```bash npm2yarn +npm install medusa-plugin-meilisearch@1.0.0 +``` + +--- + +## Actions required + +As you can see from the new object shape, the property `indexSettings` has been introduced to hold the settings specific to Meilisearch’s index options. This has been done to make space for new settings like `transformer` and `primaryKey`. + +Previously, your Meilisearch plugin configurations were something like this: + +```js title=medusa-config.js +const plugins = [ + // ... + { + config: { + host: "https://search-api-example.com", + apiKey: "some_key", + }, + settings: { + products: { + searchableAttributes: ["title", "description"], + attributesToRetrieve: ["title", "description"], + }, + }, + }, +] +``` + +In the above example, an index `products` has been configured with the two options `searchableAttributes` and `attributesToRetrieve`. Updating to 1.0.0 requires you to nest these options within the `indexSettings`. + +The updated plugin options would look like so: + +```js +const plugins = [ + // ... + { + config: { + host: "https://search-api-example.com", + apiKey: "some_key", + }, + settings: { + products: { + indexSettings: { + searchableAttributes: ["title", "description"], + attributesToRetrieve: ["title", "description"], + }, + }, + }, + }, +] +``` + +You can learn more about the new settings in the [MeiliSearch plugin documentation](../../../plugins/search/meilisearch.md) \ No newline at end of file diff --git a/docs/content/upgrade-guides/plugins/meilisearch/_category_.json b/docs/content/upgrade-guides/plugins/meilisearch/_category_.json new file mode 100644 index 0000000000..1bda7e98e4 --- /dev/null +++ b/docs/content/upgrade-guides/plugins/meilisearch/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "MeiliSearch", + "customProps": { + "reverse": true + } +} \ No newline at end of file diff --git a/docs/content/user-guide.mdx b/docs/content/user-guide.mdx index 5442d7aa5f..11a77ab7f0 100644 --- a/docs/content/user-guide.mdx +++ b/docs/content/user-guide.mdx @@ -23,7 +23,7 @@ To access the admin panel of your ecommerce store, you must have a URL to the de Once you open the URL, you’ll be asked to log in. -![Log In Page](https://res.cloudinary.com/dza7lstvk/image/upload/v1667923937/Medusa%20Docs/User%20Guide/cUYLszZ_bksyzs.png) +![Log In Page](https://res.cloudinary.com/dza7lstvk/image/upload/v1680249817/Medusa%20Docs/User%20Guide/Screenshot_2023-03-31_at_11.02.49_AM_uiv0hl.png) You must use your user’s email and password to log in. If you’re unsure what your email and password are, please contact the technical personnel that deployed your Medusa backend and admin. diff --git a/docs/content/user-guide/customers/_category_.json b/docs/content/user-guide/customers/_category_.json index eede8e0504..d184c9508d 100644 --- a/docs/content/user-guide/customers/_category_.json +++ b/docs/content/user-guide/customers/_category_.json @@ -1,5 +1,5 @@ { - "position": 5, + "position": 4, "collapsed": false, "link": null, "label": "Customers", diff --git a/docs/content/user-guide/multiwarehouse/_category_.json b/docs/content/user-guide/multiwarehouse/_category_.json new file mode 100644 index 0000000000..d91e7032a4 --- /dev/null +++ b/docs/content/user-guide/multiwarehouse/_category_.json @@ -0,0 +1,9 @@ +{ + "position": 5, + "collapsed": false, + "link": null, + "label": "Multi-Warehouse", + "customProps": { + "sidebar_is_group_headline": true + } +} \ No newline at end of file diff --git a/docs/content/user-guide/multiwarehouse/index.md b/docs/content/user-guide/multiwarehouse/index.md new file mode 100644 index 0000000000..3b45cfcb9c --- /dev/null +++ b/docs/content/user-guide/multiwarehouse/index.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 1 +description: "Multi-warehouse allows you to define multiple locations that your stock items are available in. In the Medusa admin, you can manage the store's inventory and location." +--- + +# Multi-Warehouse Overview + +In this document, you’ll get an overview of the multi-warehouse setup and how you can use it in the Medusa admin. + +:::note + +Multi-warehouse requires the Inventory and Stock Location modules to be installed. If you’re unsure how to handle that, please contact your technical team for support. + +::: + +## Overview + +Multi-warehouse allows you to define multiple locations that your stock items are available in. You can then specify for each of these locations the quantity of the item. You can also associate locations with multiple [Sales Channels](../sales-channels/index.md). + +When fulfilling an order, you can select the location to fulfill the items from. Similarly, when requesting or handling a return, you can select the location to return the item to. This will automatically increment or decrement the quantity as necessary. + +It should be noted that the following are not explained within this section of the user guide: + +- Item allocation and locations in orders: this is explained in the [Orders section](../orders/manage.mdx). +- Managing general inventory of a product variant: this is explained in the [Products section](../products/manage.mdx). + +--- + +## Learn More About Multi-Warehouse + +- [Manage Inventory](./inventory.mdx) +- [Manage Locations](./locations.mdx) diff --git a/docs/content/user-guide/multiwarehouse/inventory.mdx b/docs/content/user-guide/multiwarehouse/inventory.mdx new file mode 100644 index 0000000000..2426ece4b4 --- /dev/null +++ b/docs/content/user-guide/multiwarehouse/inventory.mdx @@ -0,0 +1,30 @@ +--- +sidebar_position: 2 +description: "This user guide explains how to manage inventory in the Medusa Admin. Learn how to check and change a variant's quantity in a location." +addHowToData: true +--- + +import UiIcon from '@site/src/components/UiIcon'; + +# Manage Inventory + +In this document, you’ll learn how to manage inventory in the Medusa Admin. + +## Check Inventory + +To check your store’s inventory, go to the Inventory page. Here, you’ll see the available inventory of product variants in a location. + +You can switch locations from the select field above the table of inventory. You can also search products through the search field above the table of inventory. + +--- + +## Change Variant Inventory + +To change a product variant’s inventory in a location: + +1. Go to the Inventory page. +2. Choose the location you want to change the inventory in from the select field above the table. +3. Find the product variant you want to change its quantity, then click on the icon at its right. +4. Choose “Adjust Availability” from the dropdown. +5. In the new window that opens, change the quantity of the variant. +6. Once done, click on the “Save and close” button. diff --git a/docs/content/user-guide/multiwarehouse/locations.mdx b/docs/content/user-guide/multiwarehouse/locations.mdx new file mode 100644 index 0000000000..e0ce1d70ef --- /dev/null +++ b/docs/content/user-guide/multiwarehouse/locations.mdx @@ -0,0 +1,80 @@ +--- +sidebar_position: 3 +description: "This user guide explains how to manage inventory in the Medusa Admin. Learn how to check and change a variant's quantity in a location." +addHowToData: true +--- + +import UiIcon from '@site/src/components/UiIcon'; + +# Manage Locations + +In this document, you’ll learn how to manage the stock locations using the Medusa admin. + +## Create a Location + +To create a location: + +1. Go to the Inventory page. +2. Click on the Locations header at the top of the section. +3. Click on the “Add location” button at the top right. +4. In the new form that opens: + 1. Enter the location name. + 2. Optionally, enter the address details of the location. + 3. To associate this location with sales channels: + 1. Click on the Sales Channels collapsible. + 2. Click on the “Add sales channels” button. + 3. In the new window that opens: + 1. Click on the “Add Channels” button. + 2. Choose the channels you want to add. + 3. Click the “Save and close” button. +5. Once you’re done, click on the “Add location” button. + +--- + +## Edit a Location + +To edit a location: + +1. Go to the Inventory page. +2. Click on the Locations header at the top of the section. +3. Find the location you want to edit and click on the icon at its right. +4. Choose “Edit details” from the dropdown. +5. Edit any of the location’s details. +6. Once you’re done, click the “Save and close” button. + +--- + +## Edit Sales Channels of a Location + +To edit the sales channels of a location: + +1. Go to the Inventory page. +2. Click on the Locations header at the top of the section. +3. Find the location you want to edit its sales channels, and click on the “Edit channels” button. +4. In the new window that opens: + 1. To remove an association to a sales channel: + 1. Click on the checkbox next to the sales channel. + 2. Click on the Remove button. + 2. To associate a sales channel: + 1. Click on the “Add Channels” button. + 2. Choose the channels you want to add. + 3. Click the “Save and go back” button. +5. Once you’re done, click the Close button. + +--- + +## Delete a Location + +:::warning + +Deleting a location can’t be undone, and its data can’t be restored. + +::: + +To delete a location: + +1. Go to the Inventory page. +2. Click on the Locations header at the top of the section. +3. Find the location you want to delete and click on the icon at its right. +4. Choose “Delete” from the dropdown. +5. Enter the name of the location then click the “Yes, confirm” button. diff --git a/docs/content/user-guide/orders/claims.mdx b/docs/content/user-guide/orders/claims.mdx index 842b9eb02a..84cbf96860 100644 --- a/docs/content/user-guide/orders/claims.mdx +++ b/docs/content/user-guide/orders/claims.mdx @@ -26,6 +26,12 @@ If you create a claim of type Refund, a refund is issued to the customer right a ::: +:::info + +The steps mentioned here regarding an item's location and quantity are only available if you have the Inventory and Stock Location modules installed. You can learn more in the [Multi-Warehouse documentation](../multiwarehouse/index.md). + +::: + 1. Open the order details page. 2. In the Timeline section, click on the icon at the top right. 3. Click on Register Claim in the dropdown. @@ -38,6 +44,7 @@ If you create a claim of type Refund, a refund is issued to the customer right a - Choose the Shipping method to use to return the item. - You can add a custom price for the shipping method by clicking on the Add custom price button. - You can remove the custom price by clicking on the icon next to the price. + - For the Location field, you can optionally choose the location that the items should be returned to. - Choose whether the type of the claim is “Refund” or “Replace”. If you choose “Replace”: - Choose the products you want to send to the customer by clicking Add Product and choosing the product you want to add. - You can remove a product from the “items to send” list by clicking on the icon. diff --git a/docs/content/user-guide/orders/exchange.mdx b/docs/content/user-guide/orders/exchange.mdx index 04c28c5f89..a5e3633ea8 100644 --- a/docs/content/user-guide/orders/exchange.mdx +++ b/docs/content/user-guide/orders/exchange.mdx @@ -21,7 +21,8 @@ To find customers’ requested order exchanges: :::info -An exchange can only be created if the order’s payment is captured and the items have been fulfilled. +- An exchange can only be created if the order’s payment is captured and the items have been fulfilled. +- The steps mentioned here regarding an item's location and quantity are only available if you have the Inventory and Stock Location modules installed. You can learn more in the [Multi-Warehouse documentation](../multiwarehouse/index.md). ::: @@ -39,12 +40,13 @@ To create an order exchange: 2. Choose a Shipping Method to return the customer’s items. 1. You can optionally specify a custom price for shipping by clicking the “Add custom price” button. 2. You can remove the custom price by clicking the icon. - 3. In the “Items to send” section, click on the Add Product button. Then: + 3. For the Location field, you can optionally choose the location that the items should be returned to. + 4. In the “Items to send” section, click on the Add Product button. Then: 1. Check the products you want to send to the customer in exchange. 2. When you’re done, click the Add button. 3. You can remove products you’ve added by clicking the icon. 4. You can change the quantity of the product to send using the and icons. - 4. If you don’t want the customer to receive an email that an exchange has been registered, uncheck the “Send notifications” checkbox. + 5. If you don’t want the customer to receive an email that an exchange has been registered, uncheck the “Send notifications” checkbox. 5. Once you’re done, click the Complete button. ## Mark an Exchange’s Return as Received diff --git a/docs/content/user-guide/orders/fulfillments.mdx b/docs/content/user-guide/orders/fulfillments.mdx index 380e3c80e1..41bb3f0094 100644 --- a/docs/content/user-guide/orders/fulfillments.mdx +++ b/docs/content/user-guide/orders/fulfillments.mdx @@ -25,7 +25,7 @@ If some items of the order are fulfilled, the order’s fulfillment status will :::info -You can only create a fulfillment in an order while there are items in the order that haven’t been shipped. +The steps mentioned here regarding an item's location and quantity are only available if you have the Inventory and Stock Location modules installed. You can learn more in the [Multi-Warehouse documentation](../multiwarehouse/index.md). ::: @@ -35,10 +35,11 @@ To create a fulfillment for an order: 2. Scroll down to the Fulfillment section. 3. Click on the Create Fulfillment button. 4. In the window that opens: - - Choose the items you want to create the fulfillment for. These are the items you’ll ship together. You can also choose a specific quantity of an item to ship using the and icons under the Quantity column. + - Choose the location you want to fulfill the items from. + - Under Items to fulfill, enter the quantity of the items you want to fulfill. - Optionally enter any additional information you want to associate with the fulfillment under the Metadata section. - If you don’t want the customer to receive an email that a fulfillment has been created, uncheck the “Send notifications” checkbox. -5. Once done, click on the Complete button. +5. Once done, click on the "Create fulfillment" button. You can check the fulfillment details in the Fulfillment and Timeline sections. diff --git a/docs/content/user-guide/orders/manage.mdx b/docs/content/user-guide/orders/manage.mdx index 32b0b0fc91..102bf9e47b 100644 --- a/docs/content/user-guide/orders/manage.mdx +++ b/docs/content/user-guide/orders/manage.mdx @@ -50,6 +50,56 @@ This section includes the customer’s details, including their shipping address --- +## Manage Item Allocation + +:::note + +This feature is only available if you have the Inventory and Stock Locations module installed. You can learn more in the [Multi-Warehouse documentation](../multiwarehouse/index.md). + +::: + +Item allocation occurs when a customer places an order. The orderd quantity of the items is considered reserved of the underlying product variant's quantity in a stock location. This is only applied for product variants that have "Manage Inventory" enabled. + +You can manage the item's allocation by managing the location that the item's quantity will be reserved from. + +### Allocate an Item + +Items should be allocated by default. However, in some edge cases the item may need to be allocated manually. + +To allocate an Item: + +1. Go to the order details page. +2. Scroll to the Summary section. +3. You should see an "Awaits allocation" badge at the top of the Summary section. Click on it. +4. In the form that opens: + 1. For the Location field, choose the location to allocate the item from. + 2. In the Items to Allocate section, choose the quantity to allocate for each item. +5. Once you're done, click the "Save allocation" button. + +### Edit Item Allocation + +If an item is already allocated, you can edit its allocation by following these steps: + +1. Go to the order details page. +2. Scroll to the Summary section. +3. Find the item you want to change its allocation. If the item is already allocated, you should see a icon next to it. +4. Hover over the icon, then click the "Edit Allocation" button. +5. In the new window that opens: + 1. In the Locations field, choose the location to allocate this item from. + 2. Once done, click the "Save and close" button. + +### Delete Item Allocation + +To delete an item allocation: + +1. Go to the order details page. +2. Scroll to the Summary section. +3. Find the item you want to delete its allocation. If the item is already allocated, you should see a check icon next to it. +4. Hover over the icon, then click the "Edit Allocation" button. +5. In the new window that opens, click on the "Delete allocation" button. + +--- + ## Edit the Shipping Address To edit the shipping address used for an order: diff --git a/docs/content/user-guide/orders/returns.mdx b/docs/content/user-guide/orders/returns.mdx index cada6cfe4a..0aec6ddf25 100644 --- a/docs/content/user-guide/orders/returns.mdx +++ b/docs/content/user-guide/orders/returns.mdx @@ -35,6 +35,12 @@ To find return requests of an order: ## Request a Return for an Order +:::info + +The steps mentioned here regarding an item's location and quantity are only available if you have the Inventory and Stock Location modules installed. You can learn more in the [Multi-Warehouse documentation](../multiwarehouse/index.md). + +::: + To request a return for an order: 1. Open the order details page. @@ -44,6 +50,7 @@ To request a return for an order: - Choose the items in the order that you want to request the return. - For each item that you choose, click on the Select Reason below that item. - Choose a [Return Reason](../settings/return-reasons.mdx#add-return-reason), optionally enter a Note, then click the Add button. + - For the Location field, you can optionally choose the location you want these items to be returned to. - Select a shipping method you want to use for this return. - If you want to change the default price of the shipping method, click on the “Add custom price” button and enter the price you want to use. - If you want to remove the custom price, you can click on the icon. diff --git a/docs/content/user-guide/products/_category_.json b/docs/content/user-guide/products/_category_.json index d391df024d..90be6981f9 100644 --- a/docs/content/user-guide/products/_category_.json +++ b/docs/content/user-guide/products/_category_.json @@ -1,5 +1,5 @@ { - "position": 4, + "position": 3, "collapsed": false, "link": null, "label": "Products", diff --git a/docs/content/user-guide/products/categories.mdx b/docs/content/user-guide/products/categories.mdx new file mode 100644 index 0000000000..2657890659 --- /dev/null +++ b/docs/content/user-guide/products/categories.mdx @@ -0,0 +1,78 @@ +--- +sidebar_position: 3 +description: 'This user guide explains how to manage categories on the Medusa admin. Learn how to create a category, edit existing categories, delete a category, and more.' +addHowToData: true +--- + +import UiIcon from '@site/src/components/UiIcon'; + +# Manage Categories + +In this document, you’ll learn how to create a category, edit existing categories, delete a category, and more. + +:::note + +Product Categories are available as a beta feature and must be enabled first. If you want to use them, ask your technical team to enable them first. + +::: + +## Create a Category + +To create a product category: + +1. Go to the Categories Page. +2. Click on the “Add category” button at the top right. +3. In the form that opens: + 1. Enter the name of the category. This is the only required field. + 2. You can enter a handle if you don’t want to use a default handle. A handle is generally used to format the URL shown on your storefront. The default handle will lower-case the category name, and replace spaces with a dash (-). For example, if you name the category “Men Clothing” the handle will be `men-clothing`. + 3. You can change the Status to “Inactive” if you’re creating the category, but you’re still not ready to use it. + 4. You can change the Visibility to “Private” if you’re creating an internal category that shouldn’t be shown to the customer. +4. Once you’re done, click the “Save category” button. + +--- + +## Edit a Category + +To edit a product category: + +1. Go to the Categories page. +2. Click on the icon at the right of the category you want to edit. +3. Choose Edit from the dropdown. +4. In the form that opens, you can edit any of the fields of the category. +5. Once you’re done, click the “Save and close” button. + +--- + +## Add a Nested Category + +To add a new category that should be nested inside another category: + +1. Go to the Categories page. +2. Click on the icon at the right of the category you’re adding the new category inside. +3. In the form that opens, enter the details as described in the [Create a Category section](#create-a-category). +4. Once you’re done, click on the “Save category” button. + +--- + +## Organize Categories + +To organize categories and adjust their hierarchy: + +1. Go to the Categories page. +2. Click and hold the icon next to the category you want to move. Keep holding the icon until step four. +3. Move the category to the new destination. You can make it a parent category to another category, nest it inside another category, or move it to the top hierarchy. +4. Once you move it to the new destination, release the icon. + +--- + +## Delete a Category + +:::warning + +Deleting a category can’t be undone, and its data can’t be restored. + +::: + +1. Go to the Categories page. +2. Click on the icon at the right of the category. +3. Choose Delete from the dropdown. diff --git a/docs/content/user-guide/products/collections.mdx b/docs/content/user-guide/products/collections.mdx index 177f12c825..fd2bb5e8ec 100644 --- a/docs/content/user-guide/products/collections.mdx +++ b/docs/content/user-guide/products/collections.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 description: 'This user guide explains how to manage collections on the Medusa admin. Learn how to create, edit, and delete collections.' addHowToData: true --- diff --git a/docs/content/user-guide/products/export.mdx b/docs/content/user-guide/products/export.mdx index 0987af38c6..fde1c7e2e0 100644 --- a/docs/content/user-guide/products/export.mdx +++ b/docs/content/user-guide/products/export.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 description: 'This user guide explains how to export a list of products on the Medusa admin.' addHowToData: true --- diff --git a/docs/content/user-guide/products/import.mdx b/docs/content/user-guide/products/import.mdx index b092a49500..9aac112ade 100644 --- a/docs/content/user-guide/products/import.mdx +++ b/docs/content/user-guide/products/import.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 description: 'This user guide explains how to import a list of products into Medusa using the admin.' addHowToData: true --- diff --git a/docs/content/user-guide/products/index.mdx b/docs/content/user-guide/products/index.mdx index 378dcec3c5..82c5c032b7 100644 --- a/docs/content/user-guide/products/index.mdx +++ b/docs/content/user-guide/products/index.mdx @@ -15,7 +15,9 @@ Products are the goods you sell in your store. They can be physical or digital p You can create products and edit their details. That includes basic info, sales channels, inventory management, prices management, and much more. -Part of the products domain is Collections. Collections are used to split products based on their type or function. For example, if you sell furniture and clothing, you can have two collections: Furniture and Clothing. +Part of the product domain is Categories. Categories allow you to organize products into more than one category. You can also manage the hierarchy of the category. An example of a category could be "Women" with "Shirt" as a nested category. + +Another part of the domain is Collections. Collections are used to split products for a marketing or sales purpose. For example, you can create a "Summer Collection" to showcase products for the summer. --- @@ -50,6 +52,7 @@ In the list, you can see collection details such as the title, handle, and the n ## Learn More About Products - [Manage Products](./manage.mdx) +- [Manage Categories](./categories.mdx) - [Manage Collections](./collections.mdx) - [Export Products](./export.mdx) - [Import Products](./import.mdx) \ No newline at end of file diff --git a/docs/content/user-guide/products/manage.mdx b/docs/content/user-guide/products/manage.mdx index 334f9d2a80..27febc40f1 100644 --- a/docs/content/user-guide/products/manage.mdx +++ b/docs/content/user-guide/products/manage.mdx @@ -28,11 +28,18 @@ This opens a new form with different sections. The only field required is the Ti ### Organize Section +:::note + +If you don't see a categories field, it's because this feature is currently in beta and must be manually enabled by your technical team. + +::: + All fields in this section allow you to better group and filter your products using types, collections, and tags. 1. You can either choose from a list of types or create a new one. 2. You can choose from a list of collections. -3. Tags are comma-separated. After entering a tag’s value, add a comma “,” and it will create the tag. You can remove the tag by pressing the icon next to it. +3. You can choose one or more categories. If you don't have any categories, you can learn how to create them in the [Manage Categories documentation](./categories.mdx). If you want to choose a nested category, you can click on a parent category which will show the categories nested inside it. +4. Tags are comma-separated. After entering a tag’s value, add a comma “,” and it will create the tag. You can remove the tag by pressing the icon next to it. ### Variants Section @@ -94,6 +101,8 @@ To save the product you can either: 1. Click the “Save as draft” button to save the product without publishing it. 2. Click the “Publish product” button to save the product and publish it. +--- + ## View Product Details To view a product’s details: @@ -101,6 +110,8 @@ To view a product’s details: 1. Go to the Products page. 2. Click on the product you want to view. +--- + ## Edit a Product’s General Information To edit a product’s general information: @@ -111,6 +122,8 @@ To edit a product’s general information: 4. Edit any of the information in the window. 5. Once done, click on the Save button. +--- + ## Change a Product’s Status A product’s status can either be Draft or Published. @@ -121,6 +134,8 @@ To change a product’s status: 2. Click on the status at the top right of the first section. For example, if the current status of the product is Draft, you should see Draft written at the top right of the first section. 3. Click on the status in the dropdown to change the status. +--- + ## Manage Product Options :::info @@ -140,13 +155,18 @@ To manage a product’s option: 3. You can delete an option by clicking the icon. 5. Once you’re done, click on the "Save and close" button. +--- + ## Manage Product Variants ### Add a Variant :::info -Multiple variants can’t have the same options combination. +Please note the following: + +- Multiple variants can’t have the same options combination. +- The steps regarding managing the inventory of a product variant requires the Inventory and Stock Location modules to be installed. You can refer to the [Multi-Warehouse user guide](../multiwarehouse/index.md) for more details about that. ::: @@ -162,8 +182,13 @@ To add a variant to a product: 1. Click on the icon next to the currency. This expands a list of regions below the currency. These are regions that use this currency. 2. Enter the price for the region you want. 4. In the Stock & Inventory section, you can manage how Medusa handles the product’s inventory, the quantity in Stock, and more. - 1. If Manage Inventory is enabled, Medusa will change the available quantity of the variant when orders or returns are made. - 2. If Allow Backorders is enabled, customers can purchase this variant even if it’s out-of-stock. + 1. If you disable the Manage Inventory field, Medusa will assume the product is always in stock. + 2. If you enable the Manage Inventory field: + 1. Under the Quantity section, click on the "Manage locations" button. + 2. Enable the locations a product is enabled in. + 3. Click on the "Save and go back" button. + 4. For each location you added, enter the quantity available in stock. + 3. If Allow Backorders is enabled, customers can purchase this variant even if it’s out-of-stock. You can only enable "Allow Backorders" if "Manage Inventory" is enabled. 5. In the Shipping section, you can specify information relevant to shipping such as Height, Weight, Country of origin, and more. 5. Once you’re done, click on the “Save and close” button. @@ -216,6 +241,8 @@ To delete a variant: 3. Click on the icon. 4. Click on Delete Variant from the dropdown. +--- + ## Edit a Product’s Attributes To edit a product’s attributes: @@ -226,6 +253,8 @@ To edit a product’s attributes: 4. In the new window that opens, you can edit any of the attribute information of the product. 5. Once you’re done, click on the Save button. +--- + ## Manage Thumbnails ### Set Thumbnail @@ -259,6 +288,8 @@ To delete a thumbnail: 2. Click on the icon at the top right of the Thumbnail section. 3. Click the Confirm button. +--- + ## Manage Images :::info @@ -280,6 +311,8 @@ To manage a product’s images 2. Click on Delete from the dropdown. 4. Once you’re done, click Save and close. +--- + ## Duplicate a Product To duplicate a product: @@ -290,6 +323,8 @@ To duplicate a product: This creates a product with the same information as the original but as a draft. +--- + ## Delete a Product :::warning diff --git a/packages/medusa-payment-paypal/README.md b/packages/medusa-payment-paypal/README.md index c0fff03ff5..c11adde860 100644 --- a/packages/medusa-payment-paypal/README.md +++ b/packages/medusa-payment-paypal/README.md @@ -4,8 +4,6 @@ Receive payments on your Medusa commerce application using PayPal. [PayPal Plugin Documentation](https://docs.medusajs.com/plugins/payment/paypal) | [Medusa Website](https://medusajs.com/) | [Medusa Repository](https://github.com/medusajs/medusa) -The paypal plugin version `>=1.3.x` requires medusa `>=1.8.x` - ## Features - Authorize payments on orders from any sales channel. diff --git a/packages/medusa-payment-stripe/README.md b/packages/medusa-payment-stripe/README.md index fff914a06b..f39a8be46b 100644 --- a/packages/medusa-payment-stripe/README.md +++ b/packages/medusa-payment-stripe/README.md @@ -4,8 +4,6 @@ Receive payments on your Medusa commerce application using Stripe. [Stripe Plugin Documentation](https://docs.medusajs.com/plugins/payment/stripe) | [Medusa Website](https://medusajs.com/) | [Medusa Repository](https://github.com/medusajs/medusa) -The stripe plugin version `>=1.2.x` requires medusa `>=1.8.x` - ## Features - Authorize payments on orders from any sales channel. diff --git a/packages/medusa-plugin-algolia/README.md b/packages/medusa-plugin-algolia/README.md index c04c1fa5ea..58c0d6f261 100644 --- a/packages/medusa-plugin-algolia/README.md +++ b/packages/medusa-plugin-algolia/README.md @@ -37,15 +37,16 @@ Provide powerful indexing and searching features in your commerce application wi 3\. In `medusa-config.js` add the following at the end of the `plugins` array: ```js - const plugins = [ - // ... - { - resolve: `medusa-plugin-algolia`, - options: { - application_id: process.env.ALGOLIA_APP_ID, - admin_api_key: process.env.ALGOLIA_ADMIN_API_KEY, - settings: { - products: { +const plugins = [ + // ... + { + resolve: `medusa-plugin-algolia`, + options: { + applicationId: process.env.ALGOLIA_APP_ID, + adminApiKey: process.env.ALGOLIA_ADMIN_API_KEY, + settings: { + products: { + indexSettings: { searchableAttributes: ["title", "description"], attributesToRetrieve: [ "id", @@ -61,10 +62,15 @@ Provide powerful indexing and searching features in your commerce application wi "images", ], }, + transform: (product) => ({ + id: product.id, + // other attributes... + }), }, }, }, - ] + }, +] ``` --- diff --git a/packages/medusa-plugin-meilisearch/README.md b/packages/medusa-plugin-meilisearch/README.md index fdcc6b056e..0f216599c7 100644 --- a/packages/medusa-plugin-meilisearch/README.md +++ b/packages/medusa-plugin-meilisearch/README.md @@ -42,29 +42,31 @@ Provide powerful indexing and searching features in your commerce application wi { resolve: `medusa-plugin-meilisearch`, options: { - // config object passed when creating an instance - // of the MeiliSearch client config: { host: process.env.MEILISEARCH_HOST, apiKey: process.env.MEILISEARCH_API_KEY, }, settings: { - // index name products: { - // MeiliSearch's setting options - // to be set on a particular index - searchableAttributes: [ - "title", - "description", - "variant_sku", - ], - displayedAttributes: [ - "title", - "description", - "variant_sku", - "thumbnail", - "handle", - ], + indexSettings: { + searchableAttributes: [ + "title", + "description", + "variant_sku", + ], + displayedAttributes: [ + "title", + "description", + "variant_sku", + "thumbnail", + "handle", + ], + }, + primaryKey: "id", + transform: (product) => ({ + id: product.id, + // other attributes... + }), }, }, }, diff --git a/www/docs/announcement.json b/www/docs/announcement.json index 9e26dfeeb6..f5f95e2dc7 100644 --- a/www/docs/announcement.json +++ b/www/docs/announcement.json @@ -1 +1 @@ -{} \ No newline at end of file +{"id":"https://github.com/medusajs/medusa/releases/tag/v1.7.15","content":"v1.7.15 is out","isCloseable":true} diff --git a/www/docs/sidebars.js b/www/docs/sidebars.js index 7bf2e16b06..cdab3dba0f 100644 --- a/www/docs/sidebars.js +++ b/www/docs/sidebars.js @@ -19,1721 +19,1938 @@ module.exports = { homepage: [ { - type: 'doc', - id: 'homepage', - label: 'Overview', + type: "doc", + id: "homepage", + label: "Overview", customProps: { - sidebar_icon: 'book-open', + sidebar_icon: "book-open", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'create-medusa-app', - label: 'Create Medusa App', + type: "doc", + id: "create-medusa-app", + label: "Create Medusa App", customProps: { - sidebar_icon: 'rocket-launch', + sidebar_icon: "rocket-launch", }, - className: 'homepage-sidebar-item', + className: "homepage-sidebar-item", }, { - type: 'html', - value: 'Browse Docs', + type: "html", + value: "Browse Docs", customProps: { - sidebar_is_group_divider: true + sidebar_is_group_divider: true, }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'ref', - id: 'modules/overview', - label: 'Commerce Modules', + type: "ref", + id: "modules/overview", + label: "Commerce Modules", customProps: { - sidebar_icon: 'puzzle' + sidebar_icon: "puzzle", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'ref', - id: 'development/overview', - label: 'Medusa Development', + type: "ref", + id: "development/overview", + label: "Medusa Development", customProps: { - sidebar_icon: 'server-stack' + sidebar_icon: "server-stack", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'admin/quickstart', - label: 'Medusa Admin', + type: "doc", + id: "admin/quickstart", + label: "Admin Dashboard", customProps: { - sidebar_icon: 'computer-desktop' + sidebar_icon: "computer-desktop", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'ref', - id: 'plugins/overview', - label: 'Plugins', + type: "ref", + id: "plugins/overview", + label: "Plugins", customProps: { - sidebar_icon: 'squares-plus' + sidebar_icon: "squares-plus", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'html', - value: 'Frontend Storefronts', + type: "html", + value: "Frontend Storefronts", customProps: { - sidebar_is_group_divider: true + sidebar_is_group_divider: true, }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'starters/nextjs-medusa-starter', - label: 'Next.js Storefront', + type: "doc", + id: "starters/nextjs-medusa-starter", + label: "Next.js Storefront", customProps: { - sidebar_icon: 'nextjs' + sidebar_icon: "nextjs", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'html', - value: 'SDKs', + type: "html", + value: "SDKs", customProps: { - sidebar_is_group_divider: true + sidebar_is_group_divider: true, }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'ref', - id: 'js-client/overview', - label: 'JavaScript Client', + type: "ref", + id: "js-client/overview", + label: "JavaScript Client", customProps: { - sidebar_icon: 'javascript' + sidebar_icon: "javascript", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'medusa-react/overview', - label: 'Medusa React', + type: "doc", + id: "medusa-react/overview", + label: "Medusa React", customProps: { - sidebar_icon: 'react' + sidebar_icon: "react", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'html', - value: 'CLI Tools', + type: "html", + value: "CLI Tools", customProps: { - sidebar_is_group_divider: true + sidebar_is_group_divider: true, }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'cli/reference', - label: 'Medusa CLI', + type: "doc", + id: "cli/reference", + label: "Medusa CLI", customProps: { - sidebar_icon: 'command-line' + sidebar_icon: "command-line", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'development/fundamentals/local-development', - label: 'Medusa Dev CLI', + type: "doc", + id: "development/fundamentals/local-development", + label: "Medusa Dev CLI", customProps: { - sidebar_icon: 'tools' + sidebar_icon: "tools", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'html', - value: 'Additional Resources', + type: "html", + value: "Additional Resources", customProps: { - sidebar_is_group_divider: true + sidebar_is_group_divider: true, }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'category', - label: 'Deploy', + type: "category", + label: "Deploy", customProps: { - sidebar_icon: 'cloud-arrow-up' + sidebar_icon: "cloud-arrow-up", }, items: [ { - type: 'category', - label: 'Backend', + type: "category", + label: "Backend", link: { - type: 'doc', - id: 'deployments/server/index' + type: "doc", + id: "deployments/server/index", }, items: [ { - type: 'doc', - id: 'deployments/server/deploying-on-heroku', - label: 'Deploy on Heroku', + type: "doc", + id: "deployments/server/deploying-on-heroku", + label: "Deploy on Heroku", customProps: { - image: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1669739927/Medusa%20Docs/Other/xNvxSkf_l230wq.png' - } + image: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1669739927/Medusa%20Docs/Other/xNvxSkf_l230wq.png", + }, }, { - type: 'doc', - id: 'deployments/server/deploying-on-digital-ocean', - label: 'Deploy on DigitalOcean', + type: "doc", + id: "deployments/server/deploying-on-digital-ocean", + label: "Deploy on DigitalOcean", customProps: { - image: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1669739945/Medusa%20Docs/Other/aahqJp4_lbtfdz.png' - } + image: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1669739945/Medusa%20Docs/Other/aahqJp4_lbtfdz.png", + }, }, { - type: 'doc', - id: 'deployments/server/deploying-on-qovery', - label: 'Deploy on Qovery', + type: "doc", + id: "deployments/server/deploying-on-qovery", + label: "Deploy on Qovery", customProps: { - image: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1669739955/Medusa%20Docs/Other/qOvY2dN_vogsxy.png' - } + image: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1669739955/Medusa%20Docs/Other/qOvY2dN_vogsxy.png", + }, }, { - type: 'doc', - id: 'deployments/server/deploying-on-railway', - label: 'Deploy on Railway', + type: "doc", + id: "deployments/server/deploying-on-railway", + label: "Deploy on Railway", customProps: { themedImage: { - light: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1669741520/Medusa%20Docs/Other/railway-light_fzuyeo.png', - dark: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1669741520/Medusa%20Docs/Other/railway-dark_kkzuwh.png' - } - } - } - ] - }, - { - type: 'category', - label: 'Admin', - link: { - type: 'doc', - id: 'deployments/admin/index' - }, - items: [ - { - type: 'doc', - id: 'deployments/admin/deploying-on-netlify', - label: 'Deploy on Netlify', - customProps: { - image: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1679574027/Medusa%20Docs/Other/gCbsCvX_h7nijn.png' - } + light: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1669741520/Medusa%20Docs/Other/railway-light_fzuyeo.png", + dark: "https://res.cloudinary.com/dza7lstvk/image/upload/v1669741520/Medusa%20Docs/Other/railway-dark_kkzuwh.png", + }, + }, }, - ] + ], }, { - type: 'category', - label: 'Storefront', + type: "category", + label: "Admin", link: { - type: 'doc', - id: 'deployments/storefront/index' + type: "doc", + id: "deployments/admin/index", }, items: [ { - type: 'doc', - id: 'deployments/storefront/deploying-next-on-vercel', - label: 'Deploy Next.js on Vercel', + type: "doc", + id: "deployments/admin/deploying-on-vercel", + label: "Deploy on Vercel", customProps: { themedImage: { - light: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1679574115/Medusa%20Docs/Other/vercel-icon-dark_llkb7l.png', - dark: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1679574132/Medusa%20Docs/Other/vercel-icon-light_obvtno.png' - } - } + light: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1679574115/Medusa%20Docs/Other/vercel-icon-dark_llkb7l.png", + dark: "https://res.cloudinary.com/dza7lstvk/image/upload/v1679574132/Medusa%20Docs/Other/vercel-icon-light_obvtno.png", + }, + }, + }, + ], + }, + { + type: "category", + label: "Storefront", + link: { + type: "doc", + id: "deployments/storefront/index", + }, + items: [ + { + type: "doc", + id: "deployments/storefront/deploying-next-on-vercel", + label: "Deploy Next.js on Vercel", + customProps: { + themedImage: { + light: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1679574115/Medusa%20Docs/Other/vercel-icon-dark_llkb7l.png", + dark: "https://res.cloudinary.com/dza7lstvk/image/upload/v1679574132/Medusa%20Docs/Other/vercel-icon-light_obvtno.png", + }, + }, }, { - type: 'doc', - id: 'deployments/storefront/deploying-gatsby-on-netlify', - label: 'Deploy Gatsby on Netlify', + type: "doc", + id: "deployments/storefront/deploying-gatsby-on-netlify", + label: "Deploy Gatsby on Netlify", customProps: { - image: 'https://res.cloudinary.com/dza7lstvk/image/upload/v1679574027/Medusa%20Docs/Other/gCbsCvX_h7nijn.png', + image: + "https://res.cloudinary.com/dza7lstvk/image/upload/v1679574027/Medusa%20Docs/Other/gCbsCvX_h7nijn.png", badge: { - variant: 'orange', - children: 'Deprecated' - } - } + variant: "orange", + children: "Deprecated", + }, + }, }, - ] + ], }, ], - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'ref', - id: 'upgrade-guides/index', - label: 'Upgrade Guides', + type: "ref", + id: "upgrade-guides/index", + label: "Upgrade Guides", customProps: { - sidebar_icon: 'cog-six-tooth' + sidebar_icon: "cog-six-tooth", }, - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'category', - label: 'Troubleshooting', + type: "category", + label: "Troubleshooting", customProps: { - sidebar_icon: 'bug' + sidebar_icon: "bug", }, items: [ { - type: 'category', - label: 'Installation Errors', + type: "category", + label: "Installation Errors", items: [ { - type: 'doc', - id: 'troubleshooting/cli-installation-errors', - label: 'Errors Installing CLI', + type: "doc", + id: "troubleshooting/cli-installation-errors", + label: "Errors Installing CLI", }, { - type: 'doc', - id: 'troubleshooting/common-installation-errors', - label: 'General Errors', + type: "doc", + id: "troubleshooting/common-installation-errors", + label: "General Errors", }, { - type: 'doc', - id: 'troubleshooting/errors-after-update', - label: 'Errors After Update', + type: "doc", + id: "troubleshooting/errors-after-update", + label: "Errors After Update", }, - ] + ], }, { - type: 'category', - label: 'Medusa Backend Errors', + type: "category", + label: "Medusa Backend Errors", items: [ { - type: 'doc', - id: 'troubleshooting/database-error', - label: 'Database SASL Error', + type: "doc", + id: "troubleshooting/database-error", + label: "Database SASL Error", }, { - type: 'doc', - id: 'troubleshooting/redis-events', - label: 'Redis not emitting events', + type: "doc", + id: "troubleshooting/redis-events", + label: "Redis not emitting events", }, { - type: 'doc', - id: 'troubleshooting/transaction-error-in-checkout', - label: 'Error 409 in checkout', + type: "doc", + id: "troubleshooting/awilix-resolution-error", + label: "Handling AwilixResolutionError", }, { - type: 'doc', - id: 'troubleshooting/missing-payment-providers', - label: 'Payment provider missing', + type: "doc", + id: "troubleshooting/transaction-error-in-checkout", + label: "Error 409 in checkout", }, - ] + { + type: "doc", + id: "troubleshooting/missing-payment-providers", + label: "Payment provider missing", + }, + ], }, { - type: 'category', - label: 'Frontend Errors', + type: "category", + label: "Frontend Errors", items: [ { - type: 'doc', - id: 'troubleshooting/cors-issues', - label: 'CORS issues', + type: "doc", + id: "troubleshooting/cors-issues", + label: "CORS issues", }, - ] + ], }, { - type: 'category', - label: 'Medusa Admin Errors', + type: "category", + label: "Admin Dashboard Errors", items: [ { - type: 'doc', - id: 'troubleshooting/signing-in-to-admin', - label: 'Signing in to Medusa Admin', + type: "doc", + id: "troubleshooting/signing-in-to-admin", + label: "Signing in to the Admin Dashboard", }, - ] + ], }, { - type: 'category', - label: 'Plugin Errors', + type: "category", + label: "Plugin Errors", items: [ { - type: 'doc', - id: 'troubleshooting/s3-acl-error', - label: 'S3 Plugin ACL Error', + type: "doc", + id: "troubleshooting/s3-acl-error", + label: "S3 Plugin ACL Error", }, - ] + ], }, { - type: 'category', - label: 'Other Errors', + type: "category", + label: "Other Errors", items: [ { - type: 'doc', - id: 'troubleshooting/documentation-error', - label: 'Documentation Error', + type: "doc", + id: "troubleshooting/documentation-error", + label: "Documentation Error", }, - ] + ], }, ], - className: 'homepage-sidebar-item' + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'contribution-guidelines', - label: 'Contribution Guidelines', + type: "doc", + id: "contribution-guidelines", + label: "Contribution Guidelines", customProps: { - sidebar_icon: 'document-text' + sidebar_icon: "document-text", }, - className: 'homepage-sidebar-item', + className: "homepage-sidebar-item", }, { - type: 'doc', - id: 'usage', - label: 'Usage', + type: "doc", + id: "usage", + label: "Usage", customProps: { - sidebar_icon: 'light-bulb' + sidebar_icon: "light-bulb", }, - className: 'homepage-sidebar-item', + className: "homepage-sidebar-item", }, ], modules: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: "ref", + id: "homepage", + label: "Back to home", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'doc', - id: 'modules/overview', - label: 'Commerce Modules', + type: "doc", + id: "modules/overview", + label: "Commerce Modules", customProps: { sidebar_is_title: true, - sidebar_icon: 'puzzle' - } + sidebar_icon: "puzzle", + }, }, { - type: 'category', - label: 'Regions and Currencies', + type: "category", + label: "Regions and Currencies", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/regions-and-currencies/overview', - label: 'Overview' + type: "doc", + id: "modules/regions-and-currencies/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/regions-and-currencies/regions', - label: 'Regions' + type: "doc", + id: "modules/regions-and-currencies/regions", + label: "Regions", }, { - type: 'link', - href: '#', - label: 'Currencies', + type: "link", + href: "#", + label: "Currencies", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/regions-and-currencies/admin/manage-regions', - label: 'Admin: Manage Regions' + type: "doc", + id: "modules/regions-and-currencies/admin/manage-regions", + label: "Admin: Manage Regions", }, { - type: 'link', - href: '#', - label: 'Admin: Manage Currencies', + type: "link", + href: "#", + label: "Admin: Manage Currencies", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/regions-and-currencies/storefront/use-regions', - label: 'Storefront: Use Regions' + type: "doc", + id: "modules/regions-and-currencies/storefront/use-regions", + label: "Storefront: Use Regions", }, - ] + ], }, { - type: 'category', - label: 'Customers', + type: "category", + label: "Customers", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/customers/overview', - label: 'Overview' + type: "doc", + id: "modules/customers/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/customers/customers', - label: 'Customers' + type: "doc", + id: "modules/customers/customers", + label: "Customers", }, { - type: 'doc', - id: 'modules/customers/customer-groups', - label: 'Customer Groups' + type: "doc", + id: "modules/customers/customer-groups", + label: "Customer Groups", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Backend: Send SignUp Email', + type: "link", + href: "#", + label: "Backend: Send SignUp Email", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/customers/admin/manage-customers', - label: 'Admin: Manage Customers' + type: "doc", + id: "modules/customers/admin/manage-customers", + label: "Admin: Manage Customers", }, { - type: 'doc', - id: 'modules/customers/admin/manage-customer-groups', - label: 'Admin: Manage Customer Groups' + type: "doc", + id: "modules/customers/admin/manage-customer-groups", + label: "Admin: Manage Customer Groups", }, { - type: 'doc', - id: 'modules/customers/storefront/implement-customer-profiles', - label: 'Storefront: Add Customer Profiles' + type: "doc", + id: "modules/customers/storefront/implement-customer-profiles", + label: "Storefront: Add Customer Profiles", }, - ] + ], }, { - type: 'category', - label: 'Products', + type: "category", + label: "Products", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/products/overview', - label: 'Overview' + type: "doc", + id: "modules/products/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Products', + type: "link", + href: "#", + label: "Products", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Collections', + type: "doc", + id: "modules/products/categories", + label: "Categories", + }, + { + type: "link", + href: "#", + label: "Collections", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Products', + type: "link", + href: "#", + label: "Admin: Manage Products", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Collections', + type: "doc", + id: "modules/products/admin/manage-categories", + label: "Admin: Manage Categories", + }, + { + type: "link", + href: "#", + label: "Admin: Manage Collections", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/products/admin/import-products', - label: 'Admin: Import Products' + type: "doc", + id: "modules/products/admin/import-products", + label: "Admin: Import Products", }, { - type: 'link', - href: '#', - label: 'Storefront: Show Products', + type: "link", + href: "#", + label: "Storefront: Show Products", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Storefront: Show Collections', + type: "link", + href: "#", + label: "Storefront: Show Collections", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, - ] + ], }, { - type: 'category', - label: 'Carts and Checkout', + type: "category", + label: "Carts and Checkout", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/carts-and-checkout/overview', - label: 'Overview', + type: "doc", + id: "modules/carts-and-checkout/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Cart', + type: "link", + href: "#", + label: "Cart", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/carts-and-checkout/shipping', - label: 'Shipping' + type: "doc", + id: "modules/carts-and-checkout/shipping", + label: "Shipping", }, { - type: 'doc', - id: 'modules/carts-and-checkout/payment', - label: 'Payment' + type: "doc", + id: "modules/carts-and-checkout/payment", + label: "Payment", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/carts-and-checkout/backend/add-fulfillment-provider', - label: 'Backend: Create Fulfillment Provider' + type: "doc", + id: "modules/carts-and-checkout/backend/add-fulfillment-provider", + label: "Backend: Create Fulfillment Provider", }, { - type: 'doc', - id: 'modules/carts-and-checkout/backend/add-payment-provider', - label: 'Backend: Create Payment Provider' + type: "doc", + id: "modules/carts-and-checkout/backend/add-payment-provider", + label: "Backend: Create Payment Provider", }, { - type: 'doc', - id: 'modules/carts-and-checkout/storefront/implement-cart', - label: 'Storefront: Implement Cart' + type: "doc", + id: "modules/carts-and-checkout/storefront/implement-cart", + label: "Storefront: Implement Cart", }, { - type: 'doc', - id: 'modules/carts-and-checkout/storefront/implement-checkout-flow', - label: 'Storefront: Implement Checkout' + type: "doc", + id: "modules/carts-and-checkout/storefront/implement-checkout-flow", + label: "Storefront: Implement Checkout", }, - ] + ], }, { - type: 'category', - label: 'Orders', + type: "category", + label: "Orders", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/orders/overview', - label: 'Overview' + type: "doc", + id: "modules/orders/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Orders', + type: "link", + href: "#", + label: "Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Swaps', + type: "link", + href: "#", + label: "Swaps", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Returns', + type: "link", + href: "#", + label: "Returns", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Claims', + type: "link", + href: "#", + label: "Claims", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Draft Orders', + type: "link", + href: "#", + label: "Draft Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Fulfillment', + type: "link", + href: "#", + label: "Fulfillment", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Backend: Send Confirmation Email', + type: "link", + href: "#", + label: "Backend: Send Confirmation Email", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/orders/backend/handle-order-claim-event', - label: 'Backend: Send Order Claim Email' + type: "doc", + id: "modules/orders/backend/handle-order-claim-event", + label: "Backend: Send Order Claim Email", }, { - type: 'link', - href: '#', - label: 'Admin: Manage Orders', + type: "link", + href: "#", + label: "Admin: Manage Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/orders/admin/edit-order', - label: 'Admin: Edit an Order' + type: "doc", + id: "modules/orders/admin/edit-order", + label: "Admin: Edit an Order", }, { - type: 'link', - href: '#', - label: 'Admin: Manage Swaps', + type: "link", + href: "#", + label: "Admin: Manage Swaps", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Returns', + type: "link", + href: "#", + label: "Admin: Manage Returns", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Claims', + type: "link", + href: "#", + label: "Admin: Manage Claims", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Draft Orders', + type: "link", + href: "#", + label: "Admin: Manage Draft Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Return Reasons', + type: "link", + href: "#", + label: "Admin: Manage Return Reasons", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Storefront: Manage Customer Orders', + type: "link", + href: "#", + label: "Storefront: Manage Customer Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Storefront: Create a Swap', + type: "link", + href: "#", + label: "Storefront: Create a Swap", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Storefront: Create a Return', + type: "link", + href: "#", + label: "Storefront: Create a Return", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/orders/storefront/handle-order-edits', - label: 'Storefront: Handle Order Edits' + type: "doc", + id: "modules/orders/storefront/handle-order-edits", + label: "Storefront: Handle Order Edits", }, { - type: 'doc', - id: 'modules/orders/storefront/implement-claim-order', - label: 'Storefront: Implement Claim Order' + type: "doc", + id: "modules/orders/storefront/implement-claim-order", + label: "Storefront: Implement Claim Order", }, - ] + ], }, { - type: 'category', - label: 'Taxes', + type: "category", + label: "Multi-Warehouse", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/taxes/overview', - label: 'Overview' + type: "doc", + id: "modules/multiwarehouse/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "doc", + id: "modules/multiwarehouse/install-modules", + label: "Install Modules", + }, + { + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Taxes', + type: "doc", + id: "modules/multiwarehouse/inventory-module", + label: "Inventory Module", + }, + { + type: "doc", + id: "modules/multiwarehouse/stock-location-module", + label: "Stock Location Module", + }, + { + type: "html", + value: "How-to", customProps: { - sidebar_is_soon: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/taxes/inclusive-pricing', - label: 'Tax Inclusive Pricing' - }, - { - type: 'html', - value: 'How-to', + type: "link", + href: "#", + label: "Admin: Manage Stock Locations", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Backend: Create Tax Provider', + type: "link", + href: "#", + label: "Admin: Manage Inventory", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Taxes', + type: "link", + href: "#", + label: "Admin: Manage Allocations in Orders", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, - { - type: 'link', - href: '#', - label: 'Admin: Manage Tax Rates', - customProps: { - sidebar_is_soon: true - } - }, - { - type: 'link', - href: '#', - label: 'Admin: Manage Tax Overrides', - customProps: { - sidebar_is_soon: true - } - }, - { - type: 'doc', - id: 'modules/taxes/storefront/manual-calculation', - label: 'Storefront: Calculate Taxes' - } - ] + ], }, { - type: 'category', - label: 'Discounts', + type: "category", + label: "Taxes", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/discounts/overview', - label: 'Overview' + type: "doc", + id: "modules/taxes/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/discounts/discounts', - label: 'Discounts', - }, - { - type: 'html', - value: 'How-to', + type: "link", + href: "#", + label: "Taxes", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_soon: true, + }, }, { - type: 'doc', - id: 'modules/discounts/admin/manage-discounts', - label: 'Admin: Manage Discounts' + type: "doc", + id: "modules/taxes/inclusive-pricing", + label: "Tax Inclusive Pricing", }, { - type: 'doc', - id: 'modules/discounts/storefront/use-discounts-in-checkout', - label: 'Storefront: Discounts in Checkout' + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, }, - ] + { + type: "link", + href: "#", + label: "Backend: Create Tax Provider", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Taxes", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Tax Rates", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Tax Overrides", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "doc", + id: "modules/taxes/storefront/manual-calculation", + label: "Storefront: Calculate Taxes", + }, + ], }, { - type: 'category', - label: 'Gift Cards', + type: "category", + label: "Discounts", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/gift-cards/overview', - label: 'Overview' + type: "doc", + id: "modules/discounts/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/gift-cards/gift-cards', - label: 'Gift Cards' + type: "doc", + id: "modules/discounts/discounts", + label: "Discounts", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/gift-cards/backend/send-gift-card-to-customer', - label: 'Backend: Send Gift Card Code' + type: "doc", + id: "modules/discounts/admin/manage-discounts", + label: "Admin: Manage Discounts", }, { - type: 'doc', - id: 'modules/gift-cards/admin/manage-gift-cards', - label: 'Admin: Manage Gift Cards' + type: "doc", + id: "modules/discounts/storefront/use-discounts-in-checkout", + label: "Storefront: Discounts in Checkout", }, - { - type: 'doc', - id: 'modules/gift-cards/storefront/use-gift-cards', - label: 'Storefront: Use Gift Cards' - }, - ] + ], }, { - type: 'category', - label: 'Price Lists', + type: "category", + label: "Gift Cards", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/price-lists/overview', - label: 'Overview' + type: "doc", + id: "modules/gift-cards/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/price-lists/price-lists', - label: 'Price Lists' + type: "doc", + id: "modules/gift-cards/gift-cards", + label: "Gift Cards", }, { - type: 'doc', - id: 'modules/price-lists/price-selection-strategy', - label: 'Price Selection Strategy' - }, - { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/price-lists/backend/override-price-selection-strategy', - label: 'Backend: Override Price Selection' + type: "doc", + id: "modules/gift-cards/backend/send-gift-card-to-customer", + label: "Backend: Send Gift Card Code", }, { - type: 'doc', - id: 'modules/price-lists/admin/manage-price-lists', - label: 'Admin: Manage Price Lists' + type: "doc", + id: "modules/gift-cards/admin/manage-gift-cards", + label: "Admin: Manage Gift Cards", }, { - type: 'doc', - id: 'modules/price-lists/admin/import-prices', - label: 'Admin: Import Prices' + type: "doc", + id: "modules/gift-cards/storefront/use-gift-cards", + label: "Storefront: Use Gift Cards", }, - ] + ], }, { - type: 'category', - label: 'Sales Channels', + type: "category", + label: "Price Lists", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/sales-channels/overview', - label: 'Overview' + type: "doc", + id: "modules/price-lists/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/sales-channels/sales-channels', - label: 'Sales Channels' + type: "doc", + id: "modules/price-lists/price-lists", + label: "Price Lists", }, { - type: 'html', - value: 'How-to', + type: "doc", + id: "modules/price-lists/price-selection-strategy", + label: "Price Selection Strategy", + }, + { + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'modules/sales-channels/admin/manage', - label: 'Admin: Manage Sales Channels' + type: "doc", + id: "modules/price-lists/backend/override-price-selection-strategy", + label: "Backend: Override Price Selection", }, { - type: 'doc', - id: 'modules/sales-channels/storefront/use-sales-channels', - label: 'Storefront: Use Sales Channels' + type: "doc", + id: "modules/price-lists/admin/manage-price-lists", + label: "Admin: Manage Price Lists", }, - ] + { + type: "doc", + id: "modules/price-lists/admin/import-prices", + label: "Admin: Import Prices", + }, + ], }, { - type: 'category', - label: 'Users', + type: "category", + label: "Sales Channels", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'modules/users/overview', - label: 'Overview', + type: "doc", + id: "modules/sales-channels/overview", + label: "Overview", }, { - type: 'html', - value: 'Architecture', + type: "html", + value: "Architecture", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Users', - customProps: { - sidebar_is_soon: true - } + type: "doc", + id: "modules/sales-channels/sales-channels", + label: "Sales Channels", }, { - type: 'link', - href: '#', - label: 'Invites', + type: "html", + value: "How-to", customProps: { - sidebar_is_soon: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'html', - value: 'How-to', - customProps: { - sidebar_is_group_divider: true - } + type: "doc", + id: "modules/sales-channels/admin/manage", + label: "Admin: Manage Sales Channels", }, { - type: 'link', - href: '#', - label: 'Backend: Send Invite', - customProps: { - sidebar_is_soon: true - } + type: "doc", + id: "modules/sales-channels/storefront/use-sales-channels", + label: "Storefront: Use Sales Channels", + }, + ], + }, + { + type: "category", + label: "Users", + collapsible: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: "doc", + id: "modules/users/overview", + label: "Overview", }, { - type: 'link', - href: '#', - label: 'Admin: Manage Profile', + type: "html", + value: "Architecture", customProps: { - sidebar_is_soon: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Users', + type: "link", + href: "#", + label: "Users", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, { - type: 'link', - href: '#', - label: 'Admin: Manage Invites', + type: "link", + href: "#", + label: "Invites", customProps: { - sidebar_is_soon: true - } + sidebar_is_soon: true, + }, }, - ] + { + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, + }, + { + type: "link", + href: "#", + label: "Backend: Send Invite", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Profile", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Users", + customProps: { + sidebar_is_soon: true, + }, + }, + { + type: "link", + href: "#", + label: "Admin: Manage Invites", + customProps: { + sidebar_is_soon: true, + }, + }, + ], }, ], core: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: "ref", + id: "homepage", + label: "Back to home", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'doc', - id: 'development/overview', - label: 'Medusa Development', + type: "doc", + id: "development/overview", + label: "Medusa Development", customProps: { sidebar_is_title: true, - sidebar_icon: 'server-stack' - } + sidebar_icon: "server-stack", + }, }, { - type: 'category', - label: 'Backend Setup', + type: "category", + label: "Backend Setup", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'development/backend/install', - label: 'Backend Quickstart', + type: "doc", + id: "development/backend/install", + label: "Backend Quickstart", }, { - type: 'doc', - id: 'development/backend/prepare-environment', - label: 'Prepare Environment' + type: "doc", + id: "development/backend/prepare-environment", + label: "Prepare Environment", }, { - type: 'doc', - id: 'development/backend/configurations', - label: 'Configurations', + type: "doc", + id: "development/backend/configurations", + label: "Configurations", }, - ] + ], }, { - type: 'category', - label: 'Architectural Concepts', + type: "category", + label: "Architectural Concepts", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'development/fundamentals/architecture-overview', - label: 'Medusa Architecture' + type: "doc", + id: "development/fundamentals/architecture-overview", + label: "Medusa Architecture", }, { - type: 'doc', - id: 'development/fundamentals/dependency-injection', - label: 'Dependency Injection' + type: "doc", + id: "development/fundamentals/dependency-injection", + label: "Dependency Injection", }, { - type: 'doc', - id: 'development/fundamentals/local-development', - label: 'Local Development' - } - ] + type: "doc", + id: "development/fundamentals/local-development", + label: "Local Development", + }, + ], }, { - type: 'category', - label: 'Basics', + type: "category", + label: "Basics", customProps: { sidebar_is_group_headline: true, }, collapsible: false, items: [ { - type: 'category', - label: 'Entities', + type: "category", + label: "Entities", items: [ { - type: 'doc', - id: 'development/entities/overview', - label: 'Overview' + type: "doc", + id: "development/entities/overview", + label: "Overview", }, { - type: 'doc', - id: 'development/entities/migrations/overview', - label: 'Migrations' + type: "doc", + id: "development/entities/migrations/overview", + label: "Migrations", }, { - type: 'ref', - id: 'references/entities/classes/Address', - label: 'Entities Reference' + type: "ref", + id: "references/entities/classes/Address", + label: "Entities Reference", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/entities/create', - label: 'Create an Entity' + type: "doc", + id: "development/entities/create", + label: "Create an Entity", }, { - type: 'doc', - id: 'development/entities/migrations/create', - label: 'Create a Migration' + type: "doc", + id: "development/entities/extend-entity", + label: "Extend an Entity", }, - ] + { + type: "doc", + id: "development/entities/migrations/create", + label: "Create a Migration", + }, + { + type: "doc", + id: "development/entities/extend-repository", + label: "Extend a Repository", + }, + ], }, { - type: 'category', - label: 'Endpoints', + type: "category", + label: "Endpoints", items: [ { - type: 'doc', - id: 'development/endpoints/overview', - label: 'Overview', + type: "doc", + id: "development/endpoints/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/endpoints/create', - label: 'Create an Endpoint' + type: "doc", + id: "development/endpoints/create", + label: "Create an Endpoint", }, { - type: 'doc', - id: 'development/endpoints/add-middleware', - label: 'Add a Middleware' + type: "doc", + id: "development/endpoints/add-middleware", + label: "Middlewares", }, - ] + { + type: "doc", + id: "development/endpoints/example-logged-in-user", + label: "Example: Logged-In User", + }, + ], }, { - type: 'category', - label: 'Services', + type: "category", + label: "Services", items: [ { - type: 'doc', - id: 'development/services/overview', - label: 'Overview' + type: "doc", + id: "development/services/overview", + label: "Overview", }, { - type: 'doc', - id: 'references/services/classes/AuthService', - label: 'Services Reference' + type: "ref", + id: "references/services/classes/AuthService", + label: "Services Reference", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/services/create-service', - label: 'Create a Service' + type: "doc", + id: "development/services/create-service", + label: "Create a Service", }, - ] + { + type: "doc", + id: "development/services/extend-service", + label: "Extend a Service", + }, + ], }, { - type: 'category', - label: 'Events', + type: "category", + label: "Modules", items: [ { - type: 'doc', - id: 'development/events/index', - label: 'Overview' + type: "doc", + id: "development/modules/overview", + label: "Overview", }, { - type: 'doc', - id: 'development/events/subscribers', - label: 'Subscribers' - }, - { - type: 'doc', - id: 'development/events/events-list', - label: 'Events Reference' - }, - { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/events/create-subscriber', - label: 'Create a Subscriber' + type: "doc", + id: "development/modules/create", + label: "Create a Module", }, - ] + { + type: "doc", + id: "development/modules/publish", + label: "Publish a Module", + }, + ], }, { - type: 'category', - label: 'Scheduled Jobs', + type: "category", + label: "Events", items: [ { - type: 'doc', - id: 'development/scheduled-jobs/overview', - label: 'Overview', + type: "doc", + id: "development/events/index", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "doc", + id: "development/events/subscribers", + label: "Subscribers", + }, + { + type: "doc", + id: "development/events/events-list", + label: "Events Reference", + }, + { + type: "html", + value: "Available Modules", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/scheduled-jobs/create', - label: 'Create a Scheduled Job' + type: "doc", + id: "development/events/modules/redis", + label: "Redis", }, - ] + { + type: "doc", + id: "development/events/modules/local", + label: "Local", + }, + { + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, + }, + { + type: "doc", + id: "development/events/create-module", + label: "Create an Event Module", + }, + { + type: "doc", + id: "development/events/create-subscriber", + label: "Create a Subscriber", + }, + ], }, { - type: 'category', - label: 'Plugins', + type: "category", + label: "Scheduled Jobs", items: [ { - type: 'doc', - id: 'development/plugins/overview', - label: 'Overview' + type: "doc", + id: "development/scheduled-jobs/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/plugins/create', - label: 'Create a Plugin' + type: "doc", + id: "development/scheduled-jobs/create", + label: "Create a Scheduled Job", }, - { - type: 'doc', - id: 'development/plugins/publish', - label: 'Publish a Plugin' - }, - ] + ], }, { - type: 'category', - label: 'Publishable API Keys', + type: "category", + label: "Plugins", items: [ { - type: 'doc', - id: 'development/publishable-api-keys/index', - label: 'Overview' + type: "doc", + id: "development/plugins/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/publishable-api-keys/admin/manage-publishable-api-keys', - label: 'Admin: Manage Publishable API Keys' + type: "doc", + id: "development/plugins/create", + label: "Create a Plugin", }, { - type: 'doc', - id: 'development/publishable-api-keys/storefront/use-in-requests', - label: 'Storefront: Use in Requests' - } - ] + type: "doc", + id: "development/plugins/publish", + label: "Publish a Plugin", + }, + ], }, - ] + { + type: "category", + label: "Publishable API Keys", + items: [ + { + type: "doc", + id: "development/publishable-api-keys/index", + label: "Overview", + }, + { + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, + }, + { + type: "doc", + id: "development/publishable-api-keys/admin/manage-publishable-api-keys", + label: "Admin: Manage Publishable API Keys", + }, + { + type: "doc", + id: "development/publishable-api-keys/storefront/use-in-requests", + label: "Storefront: Use in Requests", + }, + ], + }, + ], }, { - type: 'category', - label: 'Advanced Concepts', + type: "category", + label: "Advanced Concepts", customProps: { sidebar_is_group_headline: true, }, collapsible: false, items: [ { - type: 'category', - label: 'Notifications', + type: "category", + label: "Cache", items: [ { - type: 'doc', - id: 'development/notification/overview', - label: 'Overview' + type: "doc", + id: "development/cache/overview", + label: "Cache", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "Available Modules", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/notification/create-notification-provider', - label: 'Create a Notification Provider' + type: "doc", + id: "development/cache/modules/redis", + label: "Redis", }, - ] + { + type: "doc", + id: "development/cache/modules/in-memory", + label: "In-Memory", + }, + { + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, + }, + { + type: "doc", + id: "development/cache/create", + label: "Create a Cache Module", + }, + ], }, { - type: 'category', - label: 'File Service', + type: "category", + label: "Notifications", items: [ { - type: 'doc', - id: 'development/file-service/overview', - label: 'Overview', + type: "doc", + id: "development/notification/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Create a File Service', - customProps: { - sidebar_is_soon: true - } + type: "doc", + id: "development/notification/create-notification-provider", + label: "Create a Notification Provider", }, - ] + ], }, { - type: 'category', - label: 'Batch Jobs', + type: "category", + label: "File Service", items: [ { - type: 'doc', - id: 'development/batch-jobs/index', - label: 'Overview' + type: "doc", + id: "development/file-service/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/batch-jobs/create', - label: 'Create Batch Job Strategy' + type: "link", + href: "#", + label: "Create a File Service", + customProps: { + sidebar_is_soon: true, + }, }, - { - type: 'doc', - id: 'development/batch-jobs/customize-import', - label: 'Customize Import Strategy' - }, - ] + ], }, { - type: 'category', - label: 'Strategies', + type: "category", + label: "Batch Jobs", items: [ { - type: 'doc', - id: 'development/strategies/overview', - label: 'Overview', + type: "doc", + id: "development/batch-jobs/index", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'link', - href: '#', - label: 'Create a Strategy', - customProps: { - sidebar_is_soon: true - } + type: "doc", + id: "development/batch-jobs/create", + label: "Create Batch Job Strategy", }, { - type: 'link', - href: '#', - label: 'Override a Strategy', - customProps: { - sidebar_is_soon: true - } + type: "doc", + id: "development/batch-jobs/customize-import", + label: "Customize Import Strategy", }, - ] + ], }, { - type: 'category', - label: 'Feature Flags', + type: "category", + label: "Strategies", items: [ { - type: 'doc', - id: 'development/feature-flags/overview', - label: 'Overview', + type: "doc", + id: "development/strategies/overview", + label: "Overview", }, { - type: 'html', - value: 'How-to', + type: "html", + value: "How-to", customProps: { - sidebar_is_group_divider: true - } + sidebar_is_group_divider: true, + }, }, { - type: 'doc', - id: 'development/feature-flags/toggle', - label: 'Toggle Feature Flags' + type: "link", + href: "#", + label: "Create a Strategy", + customProps: { + sidebar_is_soon: true, + }, }, - ] + { + type: "link", + href: "#", + label: "Override a Strategy", + customProps: { + sidebar_is_soon: true, + }, + }, + ], }, - ] + { + type: "category", + label: "Feature Flags", + items: [ + { + type: "doc", + id: "development/feature-flags/overview", + label: "Overview", + }, + { + type: "html", + value: "How-to", + customProps: { + sidebar_is_group_divider: true, + }, + }, + { + type: "doc", + id: "development/feature-flags/toggle", + label: "Toggle Feature Flags", + }, + ], + }, + ], }, ], upgradeGuides: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: "ref", + id: "homepage", + label: "Back to home", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'doc', - id: 'upgrade-guides/index', - label: 'Upgrade Guides', + type: "doc", + id: "upgrade-guides/index", + label: "Upgrade Guides", customProps: { sidebar_is_title: true, - sidebar_icon: 'cog-six-tooth' - } + sidebar_icon: "cog-six-tooth", + }, }, { - type: 'category', - label: 'Backend', + type: "category", + label: "Backend", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'autogenerated', - dirName: 'upgrade-guides/medusa-core', + type: "autogenerated", + dirName: "upgrade-guides/medusa-core", customProps: { - reverse: true - } - } - ] + reverse: true, + }, + }, + ], }, { - type: 'category', - label: 'Medusa React', + type: "category", + label: "Medusa React", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'autogenerated', - dirName: 'upgrade-guides/medusa-react', + type: "autogenerated", + dirName: "upgrade-guides/medusa-react", customProps: { - reverse: true - } - } - ] + reverse: true, + }, + }, + ], }, { - type: 'category', - label: 'Medusa Admin', + type: "category", + label: "Admin Dashboard", collapsible: false, customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { - type: 'autogenerated', - dirName: 'upgrade-guides/admin', + type: "autogenerated", + dirName: "upgrade-guides/admin", customProps: { - reverse: true - } - } - ] - } + reverse: true, + }, + }, + ], + }, + { + type: "category", + label: "Plugins", + collapsed: false, + customProps: { + sidebar_is_group_headline: true, + }, + items: [ + { + type: "autogenerated", + dirName: "upgrade-guides/plugins", + }, + ], + }, ], plugins: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: "ref", + id: "homepage", + label: "Back to home", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'doc', - id: 'plugins/overview', - label: 'Plugins', + type: "doc", + id: "plugins/overview", + label: "Plugins", customProps: { sidebar_is_title: true, - sidebar_icon: 'squares-plus-solid' - } + sidebar_icon: "squares-plus-solid", + }, }, [ { - type: 'category', - label: 'Analytics', + type: "category", + label: "Analytics", link: { - type: 'doc', - id: 'plugins/analytics/index', + type: "doc", + id: "plugins/analytics/index", }, collapsible: false, customProps: { @@ -1741,218 +1958,232 @@ module.exports = { }, items: [ { - type: 'doc', - id: 'plugins/analytics/segment', - label: 'Segment', + type: "doc", + id: "plugins/analytics/segment", + label: "Segment", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Segment with the Medusa backend.' - } + iconName: "bolt-solid", + description: + "Learn how to integrate Segment with the Medusa backend.", + }, }, ], }, { - type: 'category', - label: 'CMS', + type: "category", + label: "CMS", collapsible: false, link: { - type: 'doc', - id: 'plugins/cms/index', + type: "doc", + id: "plugins/cms/index", }, customProps: { sidebar_is_group_headline: true, }, items: [ { - type: 'category', - label: 'Contentful', + type: "category", + label: "Contentful", link: { - type: 'doc', - id: 'plugins/cms/contentful/index', + type: "doc", + id: "plugins/cms/contentful/index", }, customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Contentful with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Contentful with the Medusa backend.", }, items: [ { - type: 'doc', - id: 'plugins/cms/contentful/customize-contentful', - label: 'Customize Integration', + type: "doc", + id: "plugins/cms/contentful/customize-contentful", + label: "Customize Integration", }, - ] + ], }, { - type: 'doc', - id: 'plugins/cms/strapi', - label: 'Strapi', + type: "doc", + id: "plugins/cms/strapi", + label: "Strapi", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Strapi with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Strapi with the Medusa backend.", }, }, ], }, { - type: 'category', - label: 'Notifications', + type: "category", + label: "Notifications", collapsible: false, link: { - type: 'doc', - id: 'plugins/notifications/index', + type: "doc", + id: "plugins/notifications/index", }, customProps: { sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'plugins/notifications/sendgrid', - label: 'SendGrid', + type: "doc", + id: "plugins/notifications/sendgrid", + label: "SendGrid", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate SendGrid with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate SendGrid with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/notifications/mailchimp', - label: 'Mailchimp', + type: "doc", + id: "plugins/notifications/mailchimp", + label: "Mailchimp", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Mailchimp with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Mailchimp with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/notifications/twilio-sms', - label: 'Twilio SMS', + type: "doc", + id: "plugins/notifications/twilio-sms", + label: "Twilio SMS", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Twilio SMS with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Twilio SMS with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/notifications/slack', - label: 'Slack', + type: "doc", + id: "plugins/notifications/slack", + label: "Slack", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Slack with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Slack with the Medusa backend.", }, }, ], }, { - type: 'category', - label: 'Payment', + type: "category", + label: "Payment", collapsible: false, link: { - type: 'doc', - id: 'plugins/payment/index', + type: "doc", + id: "plugins/payment/index", }, customProps: { sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'plugins/payment/klarna', - label: 'Klarna', + type: "doc", + id: "plugins/payment/klarna", + label: "Klarna", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Klarna with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Klarna with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/payment/paypal', - label: 'PayPal', + type: "doc", + id: "plugins/payment/paypal", + label: "PayPal", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate PayPal with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate PayPal with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/payment/stripe', - label: 'Stripe', + type: "doc", + id: "plugins/payment/stripe", + label: "Stripe", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Stripe with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Stripe with the Medusa backend.", }, }, ], }, { - type: 'category', - label: 'Search', + type: "category", + label: "Search", collapsible: false, link: { - type: 'doc', - id: 'plugins/search/index', + type: "doc", + id: "plugins/search/index", }, customProps: { sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'plugins/search/algolia', - label: 'Algolia', + type: "doc", + id: "plugins/search/algolia", + label: "Algolia", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Algolia with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Algolia with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/search/meilisearch', - label: 'MeiliSearch', + type: "doc", + id: "plugins/search/meilisearch", + label: "MeiliSearch", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate MeiliSearch with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate MeiliSearch with the Medusa backend.", }, }, ], }, { - type: 'category', - label: 'File Service', + type: "category", + label: "File Service", collapsible: false, link: { - type: 'doc', - id: 'plugins/file-service/index', + type: "doc", + id: "plugins/file-service/index", }, customProps: { sidebar_is_group_headline: true, }, items: [ { - type: 'doc', - id: 'plugins/file-service/minio', - label: 'MinIO', + type: "doc", + id: "plugins/file-service/minio", + label: "MinIO", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate MinIO with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate MinIO with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/file-service/s3', - label: 'S3', + type: "doc", + id: "plugins/file-service/s3", + label: "S3", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate S3 with the Medusa backend.' + iconName: "bolt-solid", + description: "Learn how to integrate S3 with the Medusa backend.", }, }, { - type: 'doc', - id: 'plugins/file-service/spaces', - label: 'Spaces', + type: "doc", + id: "plugins/file-service/spaces", + label: "Spaces", customProps: { - iconName: 'bolt-solid', - description: 'Learn how to integrate Spaces with the Medusa backend.' + iconName: "bolt-solid", + description: + "Learn how to integrate Spaces with the Medusa backend.", }, }, ], @@ -1961,51 +2192,60 @@ module.exports = { ], userGuideSidebar: [ { - type: 'doc', - id: 'user-guide', - label: 'User Guide', + type: "doc", + id: "user-guide", + label: "User Guide", customProps: { sidebar_is_title: true, - sidebar_icon: 'user' - } + sidebar_icon: "user", + }, }, { - type: 'autogenerated', - dirName: 'user-guide' - } + type: "autogenerated", + dirName: "user-guide", + }, ], servicesSidebar: [ { - type: 'ref', - id: 'development/overview', - label: 'Back to Medusa Development', + type: "ref", + id: "development/overview", + label: "Back to Medusa Development", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'html', - value: 'Services Reference', + type: "html", + value: "Services Reference", customProps: { sidebar_is_title: true, - sidebar_icon: 'folder-open' - } + sidebar_icon: "folder-open", + }, }, { - type: 'autogenerated', - dirName: 'references/services/classes', + type: "autogenerated", + dirName: "references/services/classes", }, ], jsClientSidebar: [ { - type: 'ref', - id: 'homepage', - label: 'Back to home', + type: "ref", + id: "homepage", + label: "Back to home", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, + }, + { + type: "doc", + id: "js-client/overview", + label: "Medusa JS Client", + customProps: { + sidebar_is_title: true, + sidebar_icon: "javascript", + }, }, { type: 'doc', @@ -2019,9 +2259,9 @@ module.exports = { { type: 'category', collapsed: false, - label: 'Resources', + label: "Resources", customProps: { - sidebar_is_group_headline: true + sidebar_is_group_headline: true, }, items: [ { @@ -2183,7 +2423,7 @@ module.exports = { id: 'references/js-client/classes/AdminVariantsResource', label: 'variants', }, - ] + ], }, { type: 'doc', @@ -2200,11 +2440,11 @@ module.exports = { collapsed: true, items: [ { - type: 'doc', - id: 'references/js-client/classes/LineItemsResource', - label: 'lineItems', - } - ] + type: "doc", + id: "references/js-client/classes/LineItemsResource", + label: "lineItems", + }, + ], }, { type: 'doc', @@ -2221,16 +2461,16 @@ module.exports = { }, items: [ { - type: 'doc', - id: 'references/js-client/classes/AddressesResource', - label: 'addresses' + type: "doc", + id: "references/js-client/classes/AddressesResource", + label: "addresses", }, { - type: 'doc', - id: 'references/js-client/classes/PaymentMethodsResource', - label: 'paymentMethods' - } - ] + type: "doc", + id: "references/js-client/classes/PaymentMethodsResource", + label: "paymentMethods", + }, + ], }, { type: 'doc', @@ -2262,11 +2502,11 @@ module.exports = { collapsed: true, items: [ { - type: 'doc', - id: 'references/js-client/classes/ProductVariantsResource', - label: 'variants', - } - ] + type: "doc", + id: "references/js-client/classes/ProductVariantsResource", + label: "variants", + }, + ], }, { type: 'doc', @@ -2293,30 +2533,30 @@ module.exports = { id: 'references/js-client/classes/SwapsResource', label: 'swaps', }, - ] - } + ], + }, ], entitiesSidebar: [ { - type: 'ref', - id: 'development/overview', - label: 'Back to Medusa Development', + type: "ref", + id: "development/overview", + label: "Back to Medusa Development", customProps: { sidebar_is_back_link: true, - sidebar_icon: 'back-arrow' - } + sidebar_icon: "back-arrow", + }, }, { - type: 'html', - value: 'Entities Reference', + type: "html", + value: "Entities Reference", customProps: { sidebar_is_title: true, - sidebar_icon: 'folder-open' - } + sidebar_icon: "folder-open", + }, }, { - type: 'autogenerated', - dirName: 'references/entities/classes', + type: "autogenerated", + dirName: "references/entities/classes", }, ], } diff --git a/www/docs/src/components/Badge/index.js b/www/docs/src/components/Badge/index.js index e5a27701fc..16408e24b3 100644 --- a/www/docs/src/components/Badge/index.js +++ b/www/docs/src/components/Badge/index.js @@ -1,16 +1,20 @@ import React from "react" -import styles from './styles.module.css' -import clsx from 'clsx'; +import styles from "./styles.module.css" +import clsx from "clsx" -export default function Badge ({ className, variant, children }) { +export default function Badge({ className, variant, children }) { return ( - + {children} ) -} \ No newline at end of file +} diff --git a/www/docs/src/components/Badge/styles.module.css b/www/docs/src/components/Badge/styles.module.css index 5c7e6f3f96..aae5d59ccd 100644 --- a/www/docs/src/components/Badge/styles.module.css +++ b/www/docs/src/components/Badge/styles.module.css @@ -18,4 +18,16 @@ background-color: var(--medusa-tag-orange-bg); border-color: var(--medusa-tag-orange-border); color: var(--medusa-tag-orange-text); +} + +.greenBadge { + background-color: var(--medusa-tag-green-bg); + border-color: var(--medusa-tag-green-border); + color: var(--medusa-tag-green-text); +} + +.blueBadge { + background-color: var(--medusa-tag-blue-bg); + border-color: var(--medusa-tag-blue-border); + color: var(--medusa-tag-blue-text); } \ No newline at end of file diff --git a/www/docs/src/components/Feedback/index.css b/www/docs/src/components/Feedback/index.css index 0c03afb93d..d6e2f70154 100644 --- a/www/docs/src/components/Feedback/index.css +++ b/www/docs/src/components/Feedback/index.css @@ -1,6 +1,9 @@ .feedback-container { padding-top: var(--ifm-base-margin-vertical); padding-bottom: var(--ifm-base-margin-vertical); +} + +.doc-footer .feedback-container { border-top: 1px solid var(--ifm-doc-footer-border-color); } diff --git a/www/docs/src/css/_docspage.css b/www/docs/src/css/_docspage.css index b9c4cf9f1a..0db9c5b952 100644 --- a/www/docs/src/css/_docspage.css +++ b/www/docs/src/css/_docspage.css @@ -40,6 +40,26 @@ details summary { --ifm-paragraph-margin-bottom: 0; } +.reference-table.table-col-4 th:nth-child(1), +.reference-table.table-col-4 td:nth-child(1) { + width: 20% !important; +} + +.reference-table.table-col-4 th:nth-child(2), +.reference-table.table-col-4 td:nth-child(2) { + width: 20% !important; +} + +.reference-table.table-col-4 th:nth-child(3), +.reference-table.table-col-4 td:nth-child(3) { + width: 40% !important; +} + +.reference-table.table-col-4 th:nth-child(4), +.reference-table.table-col-4 td:nth-child(4) { + width: 20% !important; +} + .reference-table th:nth-child(1), .reference-table td:nth-child(1) { width: 30%; diff --git a/www/docs/src/css/_medusa.css b/www/docs/src/css/_medusa.css index 25512d5c3b..7186ec96e4 100644 --- a/www/docs/src/css/_medusa.css +++ b/www/docs/src/css/_medusa.css @@ -134,6 +134,16 @@ --medusa-tag-orange-text: #AD5700; --medusa-tag-orange-icon: #FFB224; --medusa-tag-orange-border: #FFD386; + --medusa-tag-green-bg: #DDF3E4; + --medusa-tag-green-bg-hover: #CCEBD7; + --medusa-tag-green-text: #18794E; + --medusa-tag-green-icon: #30A46C; + --medusa-tag-green-border: #B4DFC4; + --medusa-tag-blue-bg: #E1F0FF; + --medusa-tag-blue-bg-hover: #CEE7FE; + --medusa-tag-blue-text: #006ADC; + --medusa-tag-blue-icon: #0091FF; + --medusa-tag-blue-border: #B7D9F8; } /** Dark theme **/ @@ -204,4 +214,14 @@ html[data-theme="dark"] { --medusa-tag-orange-text: #F1A10D; --medusa-tag-orange-icon: #FFB224; --medusa-tag-orange-border: #573300; + --medusa-tag-green-bg: #113123; + --medusa-tag-green-bg-hover: #133929; + --medusa-tag-green-text: #4CC38A; + --medusa-tag-green-icon: #30A46C; + --medusa-tag-green-border: #164430; + --medusa-tag-blue-bg: #102A4C; + --medusa-tag-blue-bg-hover: #0F3058; + --medusa-tag-blue-text: #52A9FF; + --medusa-tag-blue-icon: #0091FF; + --medusa-tag-blue-border: #0D3868; } \ No newline at end of file diff --git a/www/docs/src/theme/Icon/BuildingSolid/index.tsx b/www/docs/src/theme/Icon/BuildingSolid/index.tsx new file mode 100644 index 0000000000..32209b081a --- /dev/null +++ b/www/docs/src/theme/Icon/BuildingSolid/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function IconBuildingSolid (props) { + return ( + + + + ) +} \ No newline at end of file diff --git a/www/docs/src/theme/Icon/SwatchSolid/index.tsx b/www/docs/src/theme/Icon/SwatchSolid/index.tsx new file mode 100644 index 0000000000..0c90b51b9b --- /dev/null +++ b/www/docs/src/theme/Icon/SwatchSolid/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function IconSwatchSolid (props) { + return ( + + + + + ) +} \ No newline at end of file diff --git a/www/docs/src/theme/Icon/index.ts b/www/docs/src/theme/Icon/index.ts index 9bb275cc14..c3e5f27986 100644 --- a/www/docs/src/theme/Icon/index.ts +++ b/www/docs/src/theme/Icon/index.ts @@ -10,6 +10,7 @@ import IconBoltSolid from './BoltSolid'; import IconBookOpen from './BookOpen'; import IconBug from './Bug'; import IconBugAntSolid from './BugAntSolid'; +import IconBuildingSolid from './BuildingSolid'; import IconBuildingTax from './BuildingTax'; import IconCalendar from './Calendar'; import IconCashSolid from './CashSolid'; @@ -72,6 +73,7 @@ import IconSquaresPlus from './SquaresPlus'; import IconSquaresPlusSolid from './SquaresPlusSolid'; import IconStar from './Star'; import IconStripe from './Stripe'; +import IconSwatchSolid from './SwatchSolid'; import IconTagSolid from './TagSolid'; import IconTools from './Tools'; import IconToolsSolid from './ToolsSolid'; @@ -92,6 +94,7 @@ export default { 'book-open': IconBookOpen, 'bug': IconBug, 'bug-ant-solid': IconBugAntSolid, + 'building-solid': IconBuildingSolid, 'building-tax': IconBuildingTax, 'calendar': IconCalendar, 'cash-solid': IconCashSolid, @@ -154,6 +157,7 @@ export default { 'squares-plus-solid': IconSquaresPlusSolid, 'star': IconStar, 'stripe': IconStripe, + 'swatch-solid': IconSwatchSolid, 'tag-solid': IconTagSolid, 'tools': IconTools, 'tools-solid': IconToolsSolid, diff --git a/www/docs/vercel.json b/www/docs/vercel.json index 8aed3071a0..3bf2aa6b3d 100644 --- a/www/docs/vercel.json +++ b/www/docs/vercel.json @@ -464,6 +464,10 @@ { "source": "/starters/gatsby-medusa-starter", "destination": "/starters/nextjs-medusa-starter?ref=gatsby-medusa-starter" + }, + { + "source": "/deployments/admin/deploying-on-netlify", + "destination": "/deployments/admin/deploying-on-vercel" } ] } \ No newline at end of file
@@ -60,6 +60,11 @@ Description Registration Name + + +Lifetime +
+ +\- +
+ +Core services by default have the `SINGLETON` lifetime. However, some have a different lifetime which is indicated in this table. Custom services, including services in plugins, by default have the `TRANSIENT` lifetime, unless defined differently within the custom service. +
+ +\- +
+ +\- +
-Single Payment Provider +Single Payment Processor -An instance of every payment provider that extends the `AbstractPaymentService` class. +An instance of every payment processor that extends the `AbstractPaymentService` or the `AbstractPaymentProcessor` classes. -Every payment provider is registered under two names: +Every payment processor is registered under two names: -- Its camel-case name. For example, the `StripeProviderService` is registered as `stripeProviderService`. +- Its camel-case name of the processor. For example, the `StripeProviderService` is registered as `stripeProviderService`. - `pp_` followed by its identifier. For example, the `StripeProviderService` is registered as `pp_stripe`. + + +By default, it's `TRANSIENT` unless defined differently within the payment processor service. +
-All Payment Providers +All Payment Processors -An array of all payment providers that extend the `AbstractPaymentService` class. +An array of all payment processor that extend the `AbstractPaymentService` or `AbstractPaymentProcessor` class. `paymentProviders` + + +`paymentProviders` is `TRANSIENT`, and each item in it is `TRANSIENT`. +
+ +By default, it's `SINGLETON` unless defined differently within the fulfillemnt provider service. +
+ +`fulfillmentProviders` is `TRANSIENT`, and each item in it is `TRANSIENT`. +
+ +By default, it's `SINGLETON` unless defined differently within the notification provider service. +
+ +`notificationProviders` is `TRANSIENT`, and each item in it is `TRANSIENT`. +
+ +By default, it's `TRANSIENT` unless defined differently within the file service. +
+ +By default, it's `TRANSIENT` unless defined differently within the search service. +
+ +By default, it's `SINGLETON` unless defined differently within the tax provider service. +
+ +`taxProviders` is `TRANSIENT`, and each item in it is `TRANSIENT`. +
+ +By default, it's `TRANSIENT` unless defined differently within the Oauth service. +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +
+ +\- +