From 223ccb4adda6fa78eec33da632c0f8d65a6229ec Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Mon, 1 Dec 2025 20:20:01 +0200 Subject: [PATCH] docs: docs for next release (#14110) --- .../admin/environment-variables/page.mdx | 66 +++ .../app/learn/fundamentals/admin/page.mdx | 8 +- .../environment-variables/page.mdx | 36 +- www/apps/book/generated/edit-dates.mjs | 6 +- www/apps/book/public/llms-full.txt | 402 +++++++++++++++++- .../order/custom-display-id/page.mdx | 101 +++++ .../payment/payment-provider/stripe/page.mdx | 46 ++ .../promotion/concepts/page.mdx | 22 + .../how-to/admin/auth/page.mdx | 399 +++++++++++++++++ .../customers/third-party-login/page.mdx | 1 + www/apps/resources/generated/edit-dates.mjs | 10 +- www/apps/resources/generated/files-map.mjs | 8 + .../generated-commerce-modules-sidebar.mjs | 28 +- .../generated-how-to-tutorials-sidebar.mjs | 9 + www/apps/resources/sidebars/auth.mjs | 2 +- .../resources/sidebars/how-to-tutorials.mjs | 7 + www/apps/resources/sidebars/order-module.mjs | 5 + .../user-guide/app/orders/exchanges/page.mdx | 2 + .../user-guide/app/promotions/create/page.mdx | 34 +- .../user-guide/app/promotions/manage/page.mdx | 13 +- www/apps/user-guide/generated/edit-dates.mjs | 6 +- www/packages/tags/src/tags/auth.ts | 4 + www/packages/tags/src/tags/server.ts | 4 + www/packages/tags/src/tags/user.ts | 4 + 24 files changed, 1196 insertions(+), 27 deletions(-) create mode 100644 www/apps/resources/app/commerce-modules/order/custom-display-id/page.mdx create mode 100644 www/apps/resources/app/how-to-tutorials/how-to/admin/auth/page.mdx diff --git a/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx b/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx index 8a20cfeda2..db76f22f49 100644 --- a/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx +++ b/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx @@ -1,3 +1,5 @@ +import { Table } from "docs-ui" + export const metadata = { title: `${pageNumber} Environment Variables in Admin Customizations`, } @@ -88,6 +90,70 @@ Learn more about other Vite environment variables in the [Vite documentation](ht --- +## Predefined Environment Variables + +You can further customize the Medusa Admin behavior using the following predefined environment variables. You can set the environment variables in your `.env` file or in your deployment platform. + + + +The following pre-defined environment variables are available starting [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + + + + + + + + Environment Variable + + + Description + + + Default + + + + + + + + `ADMIN_AUTH_TYPE` + + + + + A string indicating the authentication method that the JS SDK instance uses in the Medusa Admin. Possible values are `session` and `jwt`. Learn more in the [JS SDK Authentication guide](!resources!/js-sdk/auth/overview). + + + + + `session` + + + + + + + `ADMIN_JWT_TOKEN_STORAGE_KEY` + + + + + A string indicating the key used to store the authentication JWT token in the browser's local storage. Only applicable if `ADMIN_AUTH_TYPE` is set to `jwt`. + + + + + `medusa_auth_token` + + + + +
+ +--- + ## Environment Variables in Production When you build the Medusa application, including the Medusa Admin, with the `build` command, the environment variables are inlined into the build. This means that you can't change the environment variables without rebuilding the application. diff --git a/www/apps/book/app/learn/fundamentals/admin/page.mdx b/www/apps/book/app/learn/fundamentals/admin/page.mdx index 71cfc22d70..fe28db9332 100644 --- a/www/apps/book/app/learn/fundamentals/admin/page.mdx +++ b/www/apps/book/app/learn/fundamentals/admin/page.mdx @@ -37,7 +37,7 @@ The next chapters will cover these two topics in detail. ### What You Can't Customize in the Medusa Admin -You can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets). You also can't customize the login page, the authentication flow, or change the Medusa logo used in the admin dashboard. +You can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets). You also can't change the Medusa logo used in the admin dashboard. If your use case requires heavy customization of the admin dashboard, you can build a custom admin dashboard using Medusa's [Admin API routes](!api!/admin). @@ -51,6 +51,8 @@ Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui) to learn ho --- -## Admin Components List +## Admin How-to Guides -To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](!resources!/admin-components) to find common components. +The [Admin How-to Guides](!resources!/how-to-tutorials#admin) provides guides that walk you through common admin customizations, such as [building common admin components](!resources!/admin-components), or [adding third-party authentication to the admin](!resources!/how-to-tutorials/how-to/admin/auth). + +Refer to these guides for practical examples of customizing the Medusa Admin dashboard. \ No newline at end of file diff --git a/www/apps/book/app/learn/fundamentals/environment-variables/page.mdx b/www/apps/book/app/learn/fundamentals/environment-variables/page.mdx index aec961263e..8b2bdc5360 100644 --- a/www/apps/book/app/learn/fundamentals/environment-variables/page.mdx +++ b/www/apps/book/app/learn/fundamentals/environment-variables/page.mdx @@ -113,7 +113,7 @@ Learn more in the [Admin Environment Variables](../admin/environment-variables/p ## Predefined Medusa Environment Variables -The Medusa application uses the following predefined environment variables that you can set: +You can further customize the Medusa application behavior using the following predefined environment variables. You can set the environment variables in your `.env` file or in your deployment platform. @@ -306,5 +306,39 @@ You should opt for setting configurations in `medusa-config.ts` where possible. \- + + + + `ADMIN_AUTH_TYPE` ([v2.12.0+](https://github.com/medusajs/medusa/releases/tag/v2.12.0)) + + + + + A string indicating the authentication method that the JS SDK instance uses in the Medusa Admin. Possible values are `session` and `jwt`. Learn more in the [JS SDK Authentication guide](!resources!/js-sdk/auth/overview). + + + + + `session` + + + + + + + `ADMIN_JWT_TOKEN_STORAGE_KEY` ([v2.12.0+](https://github.com/medusajs/medusa/releases/tag/v2.12.0)) + + + + + A string indicating the key used to store the authentication JWT token in the browser's local storage. Only applicable if `ADMIN_AUTH_TYPE` is set to `jwt`. + + + + + `medusa_auth_token` + + + diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index a9f0c02b73..2db9fffefb 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -29,7 +29,7 @@ export const generatedEditDates = { "app/learn/fundamentals/events-and-subscribers/emit-event/page.mdx": "2025-11-26T13:00:24.625Z", "app/learn/fundamentals/workflows/conditions/page.mdx": "2025-09-04T15:57:00.934Z", "app/learn/fundamentals/modules/module-link-directions/page.mdx": "2024-07-24T09:16:01+02:00", - "app/learn/fundamentals/admin/page.mdx": "2025-07-25T12:46:15.466Z", + "app/learn/fundamentals/admin/page.mdx": "2025-11-26T10:47:12.412Z", "app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-08-01T07:16:21.736Z", "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-08-01T13:11:18.823Z", "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-10-28T16:01:36.609Z", @@ -92,7 +92,7 @@ export const generatedEditDates = { "app/learn/introduction/architecture/page.mdx": "2025-07-18T15:52:35.513Z", "app/learn/fundamentals/data-models/infer-type/page.mdx": "2025-03-18T07:41:01.936Z", "app/learn/fundamentals/custom-cli-scripts/seed-data/page.mdx": "2025-09-15T16:02:51.362Z", - "app/learn/fundamentals/environment-variables/page.mdx": "2025-05-26T15:06:07.800Z", + "app/learn/fundamentals/environment-variables/page.mdx": "2025-11-26T11:05:38.863Z", "app/learn/build/page.mdx": "2025-10-27T09:30:26.957Z", "app/learn/deployment/general/page.mdx": "2025-10-21T07:39:08.998Z", "app/learn/fundamentals/workflows/multiple-step-usage/page.mdx": "2025-08-01T14:59:59.501Z", @@ -105,7 +105,7 @@ export const generatedEditDates = { "app/learn/customization/reuse-customizations/page.mdx": "2025-09-04T15:45:52.047Z", "app/learn/update/page.mdx": "2025-01-27T08:45:19.030Z", "app/learn/fundamentals/module-links/query-context/page.mdx": "2025-09-15T16:09:58.104Z", - "app/learn/fundamentals/admin/environment-variables/page.mdx": "2025-08-01T13:16:25.172Z", + "app/learn/fundamentals/admin/environment-variables/page.mdx": "2025-11-26T08:24:17.689Z", "app/learn/fundamentals/api-routes/parse-body/page.mdx": "2025-08-20T09:58:37.704Z", "app/learn/fundamentals/admin/routing/page.mdx": "2025-07-25T07:35:18.038Z", "app/learn/resources/contribution-guidelines/admin-translations/page.mdx": "2025-02-11T16:57:46.726Z", diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 6dbcb328d2..5597a3998d 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -7451,6 +7451,19 @@ Learn more about other Vite environment variables in the [Vite documentation](ht *** +## Predefined Environment Variables + +You can further customize the Medusa Admin behavior using the following predefined environment variables. You can set the environment variables in your `.env` file or in your deployment platform. + +The following pre-defined environment variables are available starting [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + +|Environment Variable|Description|Default| +|---|---|---| +|\`ADMIN\_AUTH\_TYPE\`|A string indicating the authentication method that the JS SDK instance uses in the Medusa Admin. Possible values are |\`session\`| +|\`ADMIN\_JWT\_TOKEN\_STORAGE\_KEY\`|A string indicating the key used to store the authentication JWT token in the browser's local storage. Only applicable if |\`medusa\_auth\_token\`| + +*** + ## Environment Variables in Production When you build the Medusa application, including the Medusa Admin, with the `build` command, the environment variables are inlined into the build. This means that you can't change the environment variables without rebuilding the application. @@ -7573,7 +7586,7 @@ The next chapters will cover these two topics in detail. ### What You Can't Customize in the Medusa Admin -You can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets). You also can't customize the login page, the authentication flow, or change the Medusa logo used in the admin dashboard. +You can't customize the admin dashboard's layout, design, or the content of the existing pages (aside from injecting widgets). You also can't change the Medusa logo used in the admin dashboard. If your use case requires heavy customization of the admin dashboard, you can build a custom admin dashboard using Medusa's [Admin API routes](https://docs.medusajs.com/api/admin). @@ -7587,9 +7600,11 @@ Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.m *** -## Admin Components List +## Admin How-to Guides -To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. +The [Admin How-to Guides](https://docs.medusajs.com/resources/how-to-tutorials#admin/index.html.md) provides guides that walk you through common admin customizations, such as [building common admin components](https://docs.medusajs.com/resources/admin-components/index.html.md), or [adding third-party authentication to the admin](https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth/index.html.md). + +Refer to these guides for practical examples of customizing the Medusa Admin dashboard. # Admin Routing Customizations @@ -13039,7 +13054,7 @@ Learn more in the [Admin Environment Variables](https://docs.medusajs.com/learn/ ## Predefined Medusa Environment Variables -The Medusa application uses the following predefined environment variables that you can set: +You can further customize the Medusa application behavior using the following predefined environment variables. You can set the environment variables in your `.env` file or in your deployment platform. You should opt for setting configurations in `medusa-config.ts` where possible. For a full list of Medusa configurations, refer to the [Medusa Configurations chapter](https://docs.medusajs.com/learn/configurations/medusa-config/index.html.md). @@ -13061,6 +13076,8 @@ You should opt for setting configurations in `medusa-config.ts` where possible. |\`LOG\_LEVEL\`|The allowed levels to log. Learn more in the |\`silly\`| |\`LOG\_FILE\`|The file to save logs in. By default, logs aren't saved in any file. Learn more in the |-| |\`MEDUSA\_DISABLE\_TELEMETRY\`|Whether to disable analytics data collection. Learn more in the |-| +|\`ADMIN\_AUTH\_TYPE\`|A string indicating the authentication method that the JS SDK instance uses in the Medusa Admin. Possible values are |\`session\`| +|\`ADMIN\_JWT\_TOKEN\_STORAGE\_KEY\`|A string indicating the key used to store the authentication JWT token in the browser's local storage. Only applicable if |\`medusa\_auth\_token\`| # Event Data Payload @@ -35408,6 +35425,7 @@ STRIPE_API_KEY= |\`capture\`|Whether to automatically capture payment after authorization.|No|\`false\`| |\`automatic\_payment\_methods\`|A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you integrate services like Apple pay or Google pay.|No|\`false\`| |\`payment\_description\`|A string used as the default description of a payment if none is available in cart.context.payment\_description.|No|-| +|\`oxxoExpiresDays\`|The number of days before an OXXO payment expires. Only applicable if you plan to use OXXO as a payment method.|No|\`3\`| *** @@ -35439,6 +35457,7 @@ Assuming you set the ID of the Stripe Module Provider to `stripe` in `medusa-con |iDEAL Payments|\`pp\_stripe-ideal\_stripe\`| |Przelewy24 Payments|\`pp\_stripe-przelewy24\_stripe\`| |PromptPay Payments|\`pp\_stripe-promptpay\_stripe\`| +|OXXO Payments (Available since |\`pp\_stripe-oxxo\_stripe\`| *** @@ -35464,6 +35483,7 @@ The Stripe Module Provider supports the following payment types, and the webhook |iDEAL Payments|\`\{server\_url}/hooks/payment/stripe-ideal\_stripe\`| |Przelewy24 Payments|\`\{server\_url}/hooks/payment/stripe-przelewy24\_stripe\`| |PromptPay Payments|\`\{server\_url}/hooks/payment/stripe-promptpay\_stripe\`| +|OXXO Payments (Available since |\`\{server\_url}/hooks/payment/stripe-oxxo\_stripe\`| ### Webhook Events @@ -59069,6 +59089,380 @@ const user = await userModuleService.createUsers({ ``` +# How to Add Custom Authentication in Medusa Admin + +In this guide, you'll learn how to add custom authentication provider to the Medusa Admin. + +Authenticating into the Medusa Admin with third-party providers is supported from [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + +## Overview + +By default, the Medusa Admin allows users to authenticate using their email and password. Medusa uses the [Emailpass Authentication Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/emailpass/index.html.md) to handle this authentication method. + +You can also integrate a custom or third-party authentication provider to allow users registered with that provider to log in to the Medusa Admin. This delegates the authentication process to the third-party provider, which is useful if your organization uses a centralized authentication system. + +Ensure the third-party authentication provider allows **only** your organization's users to log in to the Medusa Admin. For example, integrating a social login provider like Google or Facebook without restrictions will allow any user with an account on those platforms to access your Medusa Admin. + +### Summary of Custom Medusa Admin Authentication + +To authenticate admin users through a third-party provider, you need to: + +1. Create a [custom Authentication Module Provider](https://docs.medusajs.com/references/auth/provider/index.html.md) that integrates the third-party provider. For example, you can create a provider that integrates with Okta. +2. Change the authentication type in the Medusa Admin dashboard to use JWT authentication. +3. Add an API route with a workflow that handles creating the admin user after they authenticate through the third-party provider. +4. Add a widget on the login page that provides the option to log in through the third-party provider. + +![Overview of the custom authentication process in Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1764153489/Medusa%20Resources/admin-third-party-auth_phknrs.jpg) + +*** + +## Step 1: Create a Custom Authentication Module Provider + +The first step is to create a custom Authentication Module Provider that integrates with the third-party authentication provider you want to use. + +For example, if your organization uses Okta for authentication, you can create an Okta Authentication Provider that uses the Okta SDK to authenticate users. + +Refer to the [Create Custom Authentication Module Provider](https://docs.medusajs.com/references/auth/provider/index.html.md) guide to learn how to create a custom Authentication Module Provider. + +### Enable Custom Authentication Provider for Admin Users + +By default, registered Authentication Module Providers can be used for all [actor types](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/index.html.md). This means both admin users and customers can authenticate through the provider. + +It's recommended to restrict the custom authentication provider to admin users only. To do this, set the `http.authMethodsPerActor` configuration in `medusa-config.ts` to enable the custom authentication provider only for the `user` actor type: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + projectConfig: { + http: { + // ... + authMethodsPerActor: { + user: ["emailpass", "custom"], + customer: ["emailpass"], + }, + }, + // ... + }, +}) +``` + +Where `emailpass` is the identifier of the Emailpass Authentication Provider, and `custom` is the identifier of your custom Authentication Module Provider. + +Refer to the [Authentication Module Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers#configure-allowed-auth-providers-of-actor-types/index.html.md) guide to learn more about configuring allowed authentication providers for actor types. + +*** + +## Step 2: Change Authentication Type in Medusa Admin + +Next, you need to change the authentication type in the Medusa Admin dashboard to use JWT authentication. + +To do this, set the `ADMIN_AUTH_TYPE` environment variable to `jwt` in your Medusa application's environment variables: + +```bash +ADMIN_AUTH_TYPE=jwt +``` + +Make sure to also create a JS SDK instance in your Medusa Admin customizations with the `auth.type` option set to `jwt`. For example, create the file `src/lib/sdk.ts` with the following content: + +```ts title="src/lib/sdk.ts" +import Medusa from "@medusajs/js-sdk" + +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "jwt", + }, +}) +``` + +Refer to the [JS SDK Authentication guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/auth/overview/index.html.md) to learn more about configuring authentication in the Medusa JS SDK. + +*** + +## Step 3: Create User API Route + +Next, you need to create an [API route](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md) with a [workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) that handles creating the admin user after they authenticate through the third-party provider. + +Note that this API route will allow any user with a valid authentication token to create an admin user. Therefore, ensure that only authorized users can obtain an authentication token from the third-party provider. + +### Create User Workflow + +First, you need to create the workflow that creates a user and associates it with the authenticated identity. + +Create the file `src/workflows/create-user.ts` with the following content: + +```ts title="src/workflows/create-user.ts" +import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { createUsersWorkflow, setAuthAppMetadataStep } from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + email: string + auth_identity_id: string +} + +export const createUserWorkflow = createWorkflow( + "create-user", + (input: WorkflowInput) => { + const users = createUsersWorkflow.runAsStep({ + input: { + users: [ + { + email: input.email, + }, + ], + }, + }) + + const authUserInput = transform({ input, users }, ({ input, users }) => { + const createdUser = users[0] + + return { + authIdentityId: input.auth_identity_id, + actorType: "user", + value: createdUser.id, + } + }) + + setAuthAppMetadataStep(authUserInput) + + return new WorkflowResponse({ + user: users[0], + }) + } +) +``` + +The workflow receives an object with the following properties: + +- `email`: The email of the user being authenticated. You can modify this based on your third-party provider's identification method. +- `auth_identity_id`: The ID of the auth identity created by the custom Authentication Module Provider. Refer to the [Auth Identities](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-identity-and-actor-types/index.html.md) guide to learn more. + +In the workflow, you: + +1. Create a user with the provided email using `createUsersWorkflow`. +2. Associate the created user with the authenticated identity using `setAuthAppMetadataStep`. + +### Create User API Route + +Next, you need to create the API route that calls the workflow to create the user. + +Create the file `src/api/custom-auth/admin/users/route.ts` with the following content: + +```ts title="src/api/custom-auth/admin/users/route.ts" +import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework" +import { z } from "zod" +import { createUserWorkflow } from "../../../../workflows/create-user" + +export const CreateUserSchema = z.object({ + email: z.string(), +}) + +type CreateUserBody = z.infer + +export const POST = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => { + const user = await createUserWorkflow(req.scope) + .run({ + input: { + email: req.body.email, + auth_identity_id: req.auth_context!.auth_identity_id!, + }, + }) + + return res.status(201).json({ user }) +} +``` + +You expose a `POST` API route at `/custom-auth/admin/users` that receives the user's email in the request body. It executes `createUserWorkflow` to create the user and returns the created user in the response. + +### Apply Authentication Middleware + +Finally, you need to apply the authentication [middleware](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md) to the API route to ensure that only authenticated users can access it. + +To do this, create the file `src/api/middlewares.ts` with the following content: + +```ts title="src/api/middlewares.ts" +import { authenticate, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom-auth/admin/users", + methods: ["POST"], + middlewares: [ + authenticate("user", "bearer", { + allowUnregistered: true, + }), + ], + }, + ], +}) +``` + +This middleware applies the `authenticate` middleware to the `/custom-auth/admin/users` `POST` route, allowing only authenticated users to access it. By enabling the `allowUnregistered` option, users who are authenticated through the third-party provider but don't yet have an associated Medusa user can still access the route to create their user. + +Refer to the [Protected Routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/protected-routes/index.html.md) guide to learn more about protecting API routes with the `authentication` middleware. + +*** + +## Step 4: Add a Widget on the Login Page + +Finally, you need to add a [widget](https://docs.medusajs.com/docs/learn/fundamentals/admin/widgets/index.html.md) to the Medusa Admin login page that provides the option to log in through the third-party provider. + +The widget should display a button that authenticates or redirects the user to the third-party provider. The widget should also handle the redirection back to the Medusa Admin after successful authentication. + +For example, the widget may look like this: + +```tsx +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Button, toast } from "@medusajs/ui" +import { decodeToken } from "react-jwt" +import { useSearchParams, useNavigate } from "react-router-dom" +import { useMutation } from "@tanstack/react-query" +import { sdk } from "../lib/sdk" +import { useEffect } from "react" + +// Replace with the identifier of your custom authentication provider +const CUSTOM_AUTH_PROVIDER = "custom" + +const CustomLogin = () => { + // The third-party provider redirects back with query parameters + // used to validate the authentication + const [searchParams] = useSearchParams() + const navigate = useNavigate() + const { mutateAsync, isPending } = useMutation({ + mutationFn: async () => { + if (isPending) { + return + } + return await validateCallback() + }, + onError: (error) => { + console.error("Custom authentication error:", error) + }, + }) + + const sendCallback = async () => { + try { + return await sdk.auth.callback( + "user", + CUSTOM_AUTH_PROVIDER, + Object.fromEntries(searchParams) + ) + } catch (error) { + toast.error("Authentication failed") + throw error + } + } + + // Validate the authentication callback + const validateCallback = async () => { + const token = await sendCallback() + + const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record } + + const userExists = decodedToken.actor_id !== "" + + if (!userExists) { + // Create user + await sdk.client.fetch("/custom-auth/admin/users", { + method: "POST", + body: { + email: decodedToken.user_metadata?.email as string, + }, + }) + + const newToken = await sdk.auth.refresh() + + if (!newToken) { + toast.error("Authentication failed") + return + } + } + + // User is authenticated + navigate("/orders") + } + + // Handle custom login button click + const customLogin = async () => { + const result = await sdk.auth.login("user", CUSTOM_AUTH_PROVIDER, {}) + + if (typeof result === "object" && result.location) { + // Redirect to custom provider for authentication + window.location.href = result.location + return + } + + if (typeof result !== "string") { + // Result failed, show an error + toast.error("Authentication failed") + return + } + + navigate("/app") + } + + // Handle the redirection back from the third-party provider + useEffect(() => { + // Check for provider-specific query parameters + if (searchParams.get("code")) { + mutateAsync() + } + }, [searchParams, mutateAsync]) + + return ( + <> +
+ + + ) +} + +export const config = defineWidgetConfig({ + zone: "login.after", +}) + +export default CustomLogin +``` + +You inject the widget into the `login.after` zone. This displays it below the default email and password login form. + +You can't remove the default email and password login form from the Medusa Admin. You can only add additional login options through widgets. + +In the widget, you: + +1. Display a button that initiates login through the third-party provider using the `sdk.auth.login` method. + - If the login method returns a `location` property, redirect the user to that location to authenticate through the third-party provider. + - Otherwise, if the login method returns a token, the user is authenticated and you navigate to the Orders page of the admin dashboard. +2. Add a `useEffect` hook that checks for provider-specific query parameters in the URL, such as `code`, to handle the redirection back from the third-party provider after successful authentication. +3. Upon redirection back, you: + - Call the `sdk.auth.callback` method to validate the authentication with the third-party provider. + - If the user doesn't exist in Medusa, call the [custom API route you created](#step-3-create-user-api-route) to create the user, then refresh the authentication token. + - Finally, if authentication is successful, navigate to the Orders page of the admin dashboard. + +*** + +## Test Custom Authentication in Medusa Admin + +To test the custom authentication in the Medusa Admin, run the following command to start your Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin at `http://localhost:9000/app`, which will redirect you to the login page. + +On the login page, you should see the option to log in through the third-party provider you integrated. Click the button to log in through the provider and follow the authentication flow. + +If authentication is successful, you'll have access to the Medusa Admin dashboard. Otherwise, you can troubleshoot the issue by checking your Medusa application's logs or the browser's console for errors. + + # How-to & Tutorials In this section of the documentation, you'll find how-to guides and tutorials that will help you customize the Medusa server and admin. These guides are useful after you've learned Medusa's main concepts in the [Get Started](https://docs.medusajs.com/docs/learn/index.html.md) section of the documentation. diff --git a/www/apps/resources/app/commerce-modules/order/custom-display-id/page.mdx b/www/apps/resources/app/commerce-modules/order/custom-display-id/page.mdx new file mode 100644 index 0000000000..23259863aa --- /dev/null +++ b/www/apps/resources/app/commerce-modules/order/custom-display-id/page.mdx @@ -0,0 +1,101 @@ +export const metadata = { + title: `Custom Order Display ID`, +} + +# {metadata.title} + +In this guide, you'll learn how to customize the display ID of orders in Medusa. + + + +This feature is available since [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + + + +## Default Display ID + +By default, Medusa stores the display ID of orders in the `display_id` property of the [Order data model](/references/order/models/Order). The display ID is a serial integer that starts at 1 and increments with each new order. + +For example: + +```json +{ + "id": "order_123", + "display_id": 1, + // other properties... +} +``` + +--- + +## Custom Display ID + +In some cases, you might want to use a custom display ID for orders. This is useful for integrating with external systems or providing a more user-friendly order identifier. + +The `Order` data model has a `custom_display_id` property that stores a custom display ID you generate. + +You can define the logic for generating this ID in the `generateCustomDisplayId` module option set in `medusa-config.ts`. + +For example: + +```ts title="medusa-config.ts" +// other imports... +import { Modules } from "@medusajs/framework/utils" +import { OrderTypes, Context } from "@medusajs/framework/types" + +module.exports = defineConfig({ + modules: [ + { + key: Modules.ORDER, + options: { + generateCustomDisplayId: async function ( + order: OrderTypes.CreateOrderDTO, + sharedContext: Context + ): Promise { + // Return your custom display ID + return `${order.email}-${Date.now()}` + }, + }, + }, + // other modules... + ], + // other configurations... +}) +``` + +In the example above, the `generateCustomDisplayId` function generates a custom display ID by combining the order's email with the current timestamp. + +You can implement any logic to generate a unique and meaningful display ID for your orders. + +--- + +## View Custom Display ID in Medusa Admin + +By default, Medusa Admin displays the `display_id` in the table on the Orders page. To view the custom display ID in the table, you can enable the `view_configurations` experimental feature. + +To enable this feature, add the following to `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // other configurations... + featureFlags: { + view_configurations: true, + }, +}) +``` + +This enables the feature's flag. + +Next, run the necessary migrations: + +```bash +npx medusa db:migrate +``` + +Then, start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +Finally, customize the Order view in Medusa Admin to display the `custom_display_id` property. diff --git a/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx b/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx index 3439963b97..5d75ae46d1 100644 --- a/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx +++ b/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/page.mdx @@ -187,6 +187,28 @@ STRIPE_API_KEY= \- + + + + + + `oxxoExpiresDays` + + + + + The number of days before an OXXO payment expires. Only applicable if you plan to use OXXO as a payment method. + + + + + No + + + + + `3` + @@ -303,6 +325,18 @@ Assuming you set the ID of the Stripe Module Provider to `stripe` in `medusa-con `pp_stripe-promptpay_stripe` + + + + + + OXXO Payments (Available since [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0)) + + + + + `pp_stripe-oxxo_stripe` + @@ -413,6 +447,18 @@ The Stripe Module Provider supports the following payment types, and the webhook `{server_url}/hooks/payment/stripe-promptpay_stripe` + + + + + + OXXO Payments (Available since [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0)) + + + + + `{server_url}/hooks/payment/stripe-oxxo_stripe` + diff --git a/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx b/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx index 5af6820ce7..76e6b36255 100644 --- a/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx +++ b/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx @@ -86,6 +86,28 @@ The Medusa Admin UI may not provide a way to create each of these promotion exam --- +## Promotion Limits + + + +The `limit` property is available since [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + + + +A promotion can have usage limits to restrict how many times it can be used. + +There are three ways to limit a promotion's usage: + +1. By setting its `limit` property: This limits the total number of times the promotion can be used across all orders. +2. By setting the [global budget on the promotion's campaign](../campaign/page.mdx#global-budgets): This limits the total spend or usage across all promotions in the campaign. +3. By setting the [attribute-based budget on the promotion's campaign](../campaign/page.mdx#attribute-based-budgets): This limits the total spend or usage for specific attributes across all promotions in the campaign. For example, limiting the budget for a specific customer group. + +All budgets are applied on the promotion. Once a budget is exhausted, the promotion can no longer be applied. + +For example, if a promotion has a `limit` of `10` and its campaign has a global budget of `$1000`, the promotion can only be used `10` times or until the total discount given reaches `$1000`, whichever comes first. + +--- + ## Promotion Rules A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](/references/promotion/models/PromotionRule). diff --git a/www/apps/resources/app/how-to-tutorials/how-to/admin/auth/page.mdx b/www/apps/resources/app/how-to-tutorials/how-to/admin/auth/page.mdx new file mode 100644 index 0000000000..0bd912e674 --- /dev/null +++ b/www/apps/resources/app/how-to-tutorials/how-to/admin/auth/page.mdx @@ -0,0 +1,399 @@ +--- +tags: + - auth + - user + - name: server + label: Custom Admin Authentication +products: + - auth + - user +--- + +export const metadata = { + title: `How to Add Custom Authentication in Medusa Admin`, +} + +# {metadata.title} + +In this guide, you'll learn how to add custom authentication provider to the Medusa Admin. + + + +Authenticating into the Medusa Admin with third-party providers is supported from [Medusa v2.12.0](https://github.com/medusajs/medusa/releases/tag/v2.12.0). + + + +## Overview + +By default, the Medusa Admin allows users to authenticate using their email and password. Medusa uses the [Emailpass Authentication Provider](../../../../commerce-modules/auth/auth-providers/emailpass/page.mdx) to handle this authentication method. + +You can also integrate a custom or third-party authentication provider to allow users registered with that provider to log in to the Medusa Admin. This delegates the authentication process to the third-party provider, which is useful if your organization uses a centralized authentication system. + + + +Ensure the third-party authentication provider allows **only** your organization's users to log in to the Medusa Admin. For example, integrating a social login provider like Google or Facebook without restrictions will allow any user with an account on those platforms to access your Medusa Admin. + + + +### Summary of Custom Medusa Admin Authentication + +To authenticate admin users through a third-party provider, you need to: + +1. Create a [custom Authentication Module Provider](/references/auth/provider) that integrates the third-party provider. For example, you can create a provider that integrates with Okta. +2. Change the authentication type in the Medusa Admin dashboard to use JWT authentication. +3. Add an API route with a workflow that handles creating the admin user after they authenticate through the third-party provider. +4. Add a widget on the login page that provides the option to log in through the third-party provider. + +![Overview of the custom authentication process in Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1764153489/Medusa%20Resources/admin-third-party-auth_phknrs.jpg) + +--- + +## Step 1: Create a Custom Authentication Module Provider + +The first step is to create a custom Authentication Module Provider that integrates with the third-party authentication provider you want to use. + +For example, if your organization uses Okta for authentication, you can create an Okta Authentication Provider that uses the Okta SDK to authenticate users. + +Refer to the [Create Custom Authentication Module Provider](/references/auth/provider) guide to learn how to create a custom Authentication Module Provider. + +### Enable Custom Authentication Provider for Admin Users + +By default, registered Authentication Module Providers can be used for all [actor types](../../../../commerce-modules/auth/auth-identity-and-actor-types/page.mdx). This means both admin users and customers can authenticate through the provider. + +It's recommended to restrict the custom authentication provider to admin users only. To do this, set the `http.authMethodsPerActor` configuration in `medusa-config.ts` to enable the custom authentication provider only for the `user` actor type: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + projectConfig: { + http: { + // ... + authMethodsPerActor: { + user: ["emailpass", "custom"], + customer: ["emailpass"], + }, + }, + // ... + }, +}) +``` + +Where `emailpass` is the identifier of the Emailpass Authentication Provider, and `custom` is the identifier of your custom Authentication Module Provider. + +Refer to the [Authentication Module Providers](../../../../commerce-modules/auth/auth-providers/page.mdx#configure-allowed-auth-providers-of-actor-types) guide to learn more about configuring allowed authentication providers for actor types. + +--- + +## Step 2: Change Authentication Type in Medusa Admin + +Next, you need to change the authentication type in the Medusa Admin dashboard to use JWT authentication. + +To do this, set the `ADMIN_AUTH_TYPE` environment variable to `jwt` in your Medusa application's environment variables: + +```bash +ADMIN_AUTH_TYPE=jwt +``` + +Make sure to also create a JS SDK instance in your Medusa Admin customizations with the `auth.type` option set to `jwt`. For example, create the file `src/lib/sdk.ts` with the following content: + +```ts title="src/lib/sdk.ts" +import Medusa from "@medusajs/js-sdk" + +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "jwt", + }, +}) +``` + +Refer to the [JS SDK Authentication guide](../../../../js-sdk/auth/overview/page.mdx) to learn more about configuring authentication in the Medusa JS SDK. + +--- + +## Step 3: Create User API Route + +Next, you need to create an [API route](!docs!/learn/fundamentals/api-routes) with a [workflow](!docs!/learn/fundamentals/workflows) that handles creating the admin user after they authenticate through the third-party provider. + +Note that this API route will allow any user with a valid authentication token to create an admin user. Therefore, ensure that only authorized users can obtain an authentication token from the third-party provider. + +### Create User Workflow + +First, you need to create the workflow that creates a user and associates it with the authenticated identity. + +Create the file `src/workflows/create-user.ts` with the following content: + +```ts title="src/workflows/create-user.ts" +import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { createUsersWorkflow, setAuthAppMetadataStep } from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + email: string + auth_identity_id: string +} + +export const createUserWorkflow = createWorkflow( + "create-user", + (input: WorkflowInput) => { + const users = createUsersWorkflow.runAsStep({ + input: { + users: [ + { + email: input.email, + }, + ], + }, + }) + + const authUserInput = transform({ input, users }, ({ input, users }) => { + const createdUser = users[0] + + return { + authIdentityId: input.auth_identity_id, + actorType: "user", + value: createdUser.id, + } + }) + + setAuthAppMetadataStep(authUserInput) + + return new WorkflowResponse({ + user: users[0], + }) + } +) +``` + +The workflow receives an object with the following properties: + +- `email`: The email of the user being authenticated. You can modify this based on your third-party provider's identification method. +- `auth_identity_id`: The ID of the auth identity created by the custom Authentication Module Provider. Refer to the [Auth Identities](../../../../commerce-modules/auth/auth-identity-and-actor-types/page.mdx) guide to learn more. + +In the workflow, you: + +1. Create a user with the provided email using `createUsersWorkflow`. +2. Associate the created user with the authenticated identity using `setAuthAppMetadataStep`. + +### Create User API Route + +Next, you need to create the API route that calls the workflow to create the user. + +Create the file `src/api/custom-auth/admin/users/route.ts` with the following content: + +```ts title="src/api/custom-auth/admin/users/route.ts" +import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework" +import { z } from "zod" +import { createUserWorkflow } from "../../../../workflows/create-user" + +export const CreateUserSchema = z.object({ + email: z.string(), +}) + +type CreateUserBody = z.infer + +export const POST = async (req: AuthenticatedMedusaRequest, res: MedusaResponse) => { + const user = await createUserWorkflow(req.scope) + .run({ + input: { + email: req.body.email, + auth_identity_id: req.auth_context!.auth_identity_id!, + }, + }) + + return res.status(201).json({ user }) +} +``` + +You expose a `POST` API route at `/custom-auth/admin/users` that receives the user's email in the request body. It executes `createUserWorkflow` to create the user and returns the created user in the response. + +### Apply Authentication Middleware + +Finally, you need to apply the authentication [middleware](!docs!/learn/fundamentals/api-routes/middlewares) to the API route to ensure that only authenticated users can access it. + +To do this, create the file `src/api/middlewares.ts` with the following content: + +```ts title="src/api/middlewares.ts" +import { authenticate, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom-auth/admin/users", + methods: ["POST"], + middlewares: [ + authenticate("user", "bearer", { + allowUnregistered: true, + }), + ], + }, + ], +}) +``` + +This middleware applies the `authenticate` middleware to the `/custom-auth/admin/users` `POST` route, allowing only authenticated users to access it. By enabling the `allowUnregistered` option, users who are authenticated through the third-party provider but don't yet have an associated Medusa user can still access the route to create their user. + +Refer to the [Protected Routes](!docs!/learn/fundamentals/api-routes/protected-routes) guide to learn more about protecting API routes with the `authentication` middleware. + +--- + +## Step 4: Add a Widget on the Login Page + +Finally, you need to add a [widget](!docs!/learn/fundamentals/admin/widgets) to the Medusa Admin login page that provides the option to log in through the third-party provider. + +The widget should display a button that authenticates or redirects the user to the third-party provider. The widget should also handle the redirection back to the Medusa Admin after successful authentication. + +For example, the widget may look like this: + +```tsx title="src/admin/widgets/custom-login.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Button, toast } from "@medusajs/ui" +import { decodeToken } from "react-jwt" +import { useSearchParams, useNavigate } from "react-router-dom" +import { useMutation } from "@tanstack/react-query" +import { sdk } from "../lib/sdk" +import { useEffect } from "react" + +// Replace with the identifier of your custom authentication provider +const CUSTOM_AUTH_PROVIDER = "custom" + +const CustomLogin = () => { + // The third-party provider redirects back with query parameters + // used to validate the authentication + const [searchParams] = useSearchParams() + const navigate = useNavigate() + const { mutateAsync, isPending } = useMutation({ + mutationFn: async () => { + if (isPending) { + return + } + return await validateCallback() + }, + onError: (error) => { + console.error("Custom authentication error:", error) + }, + }) + + const sendCallback = async () => { + try { + return await sdk.auth.callback( + "user", + CUSTOM_AUTH_PROVIDER, + Object.fromEntries(searchParams) + ) + } catch (error) { + toast.error("Authentication failed") + throw error + } + } + + // Validate the authentication callback + const validateCallback = async () => { + const token = await sendCallback() + + const decodedToken = decodeToken(token) as { actor_id: string, user_metadata: Record } + + const userExists = decodedToken.actor_id !== "" + + if (!userExists) { + // Create user + await sdk.client.fetch("/custom-auth/admin/users", { + method: "POST", + body: { + email: decodedToken.user_metadata?.email as string, + }, + }) + + const newToken = await sdk.auth.refresh() + + if (!newToken) { + toast.error("Authentication failed") + return + } + } + + // User is authenticated + navigate("/orders") + } + + // Handle custom login button click + const customLogin = async () => { + const result = await sdk.auth.login("user", CUSTOM_AUTH_PROVIDER, {}) + + if (typeof result === "object" && result.location) { + // Redirect to custom provider for authentication + window.location.href = result.location + return + } + + if (typeof result !== "string") { + // Result failed, show an error + toast.error("Authentication failed") + return + } + + navigate("/app") + } + + // Handle the redirection back from the third-party provider + useEffect(() => { + // Check for provider-specific query parameters + if (searchParams.get("code")) { + mutateAsync() + } + }, [searchParams, mutateAsync]) + + return ( + <> +
+ + + ) +} + +export const config = defineWidgetConfig({ + zone: "login.after", +}) + +export default CustomLogin +``` + +You inject the widget into the `login.after` zone. This displays it below the default email and password login form. + + + +You can't remove the default email and password login form from the Medusa Admin. You can only add additional login options through widgets. + + + +In the widget, you: + +1. Display a button that initiates login through the third-party provider using the `sdk.auth.login` method. + - If the login method returns a `location` property, redirect the user to that location to authenticate through the third-party provider. + - Otherwise, if the login method returns a token, the user is authenticated and you navigate to the Orders page of the admin dashboard. +2. Add a `useEffect` hook that checks for provider-specific query parameters in the URL, such as `code`, to handle the redirection back from the third-party provider after successful authentication. +3. Upon redirection back, you: + - Call the `sdk.auth.callback` method to validate the authentication with the third-party provider. + - If the user doesn't exist in Medusa, call the [custom API route you created](#step-3-create-user-api-route) to create the user, then refresh the authentication token. + - Finally, if authentication is successful, navigate to the Orders page of the admin dashboard. + +--- + +## Test Custom Authentication in Medusa Admin + +To test the custom authentication in the Medusa Admin, run the following command to start your Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin at `http://localhost:9000/app`, which will redirect you to the login page. + +On the login page, you should see the option to log in through the third-party provider you integrated. Click the button to log in through the provider and follow the authentication flow. + +If authentication is successful, you'll have access to the Medusa Admin dashboard. Otherwise, you can troubleshoot the issue by checking your Medusa application's logs or the browser's console for errors. diff --git a/www/apps/resources/app/storefront-development/customers/third-party-login/page.mdx b/www/apps/resources/app/storefront-development/customers/third-party-login/page.mdx index 00506d07c1..157dae706d 100644 --- a/www/apps/resources/app/storefront-development/customers/third-party-login/page.mdx +++ b/www/apps/resources/app/storefront-development/customers/third-party-login/page.mdx @@ -611,6 +611,7 @@ If you already set up the [Auth Module Provider](../../../commerce-modules/auth/ - A token in a `token` property. In this case, the customer was previously logged in with the third-party service. No additional actions are required. You can use the token to send subsequent authenticated requests. 2. Once authentication with the third-party service finishes, it redirects back to the storefront with query parameters such as `code` and `state`. Make sure your third-party service is configured to redirect to your storefront's callback page after successful authentication. 3. In the storefront's callback page, send a request to the [Validate Authentication Callback API route](!api!/store#auth_postactor_typeauth_providercallback). Pass the query parameters (`code`, `state`, etc.) received from the third-party service. + - Medusa validates the authentication with the third-party service. 4. If the callback validation is successful, you'll receive the authentication token. Decode the received token in the storefront using tools like [react-jwt](https://www.npmjs.com/package/react-jwt). - If the decoded data has an `actor_id` property, the customer is already registered. Use this token for subsequent authenticated requests. - If not, follow the rest of the steps. diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 8052331ea4..b78112a0bb 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -48,7 +48,7 @@ export const generatedEditDates = { "app/commerce-modules/payment/payment/page.mdx": "2025-05-20T07:51:40.709Z", "app/commerce-modules/payment/payment-collection/page.mdx": "2025-09-01T15:10:39.107Z", "app/commerce-modules/payment/payment-flow/page.mdx": "2025-05-20T07:51:40.708Z", - "app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2025-05-20T07:51:40.709Z", + "app/commerce-modules/payment/payment-provider/stripe/page.mdx": "2025-11-24T07:47:55.273Z", "app/commerce-modules/payment/payment-provider/page.mdx": "2025-05-20T07:51:40.708Z", "app/commerce-modules/payment/payment-session/page.mdx": "2025-05-20T07:51:40.709Z", "app/commerce-modules/payment/webhook-events/page.mdx": "2025-05-20T07:51:40.710Z", @@ -70,7 +70,7 @@ export const generatedEditDates = { "app/commerce-modules/promotion/actions/page.mdx": "2025-06-27T15:42:19.142Z", "app/commerce-modules/promotion/application-method/page.mdx": "2025-10-14T12:09:22.188Z", "app/commerce-modules/promotion/campaign/page.mdx": "2025-10-13T07:34:59.008Z", - "app/commerce-modules/promotion/concepts/page.mdx": "2025-02-26T11:31:54.391Z", + "app/commerce-modules/promotion/concepts/page.mdx": "2025-12-01T09:38:54.102Z", "app/commerce-modules/promotion/page.mdx": "2025-04-17T08:48:14.643Z", "app/commerce-modules/region/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/region/_events/page.mdx": "2024-07-03T19:27:13+03:00", @@ -830,7 +830,7 @@ export const generatedEditDates = { "references/types/interfaces/types.BaseClaim/page.mdx": "2025-09-12T14:10:39.219Z", "app/commerce-modules/auth/auth-providers/github/page.mdx": "2025-01-13T11:31:35.361Z", "app/commerce-modules/auth/auth-providers/google/page.mdx": "2025-01-13T11:31:35.361Z", - "app/storefront-development/customers/third-party-login/page.mdx": "2025-11-24T07:27:46.836Z", + "app/storefront-development/customers/third-party-login/page.mdx": "2025-11-26T10:42:33.123Z", "references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-12-09T13:21:34.761Z", "references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2025-04-11T09:04:46.523Z", "references/types/WorkflowsSdkTypes/types/types.WorkflowsSdkTypes.Acknowledgement/page.mdx": "2024-12-09T13:21:35.873Z", @@ -6719,5 +6719,7 @@ export const generatedEditDates = { "references/core_flows/Product/Workflows_Product/functions/core_flows.Product.Workflows_Product.batchVariantImagesWorkflow/page.mdx": "2025-11-05T12:22:20.671Z", "app/storefront-development/guides/react-native-expo/page.mdx": "2025-11-06T07:18:45.347Z", "app/how-to-tutorials/tutorials/customer-tiers/page.mdx": "2025-11-28T08:34:06.912Z", - "app/infrastructure-modules/caching/guides/clear-cache/page.mdx": "2025-11-26T13:19:26.629Z" + "app/how-to-tutorials/how-to/admin/auth/page.mdx": "2025-11-26T11:01:53.802Z", + "app/infrastructure-modules/caching/guides/clear-cache/page.mdx": "2025-11-26T13:19:26.629Z", + "app/commerce-modules/order/custom-display-id/page.mdx": "2025-12-01T09:34:27.436Z" } \ No newline at end of file diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index 56158a813c..93e5489ac3 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -287,6 +287,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/commerce-modules/order/concepts/page.mdx", "pathname": "/commerce-modules/order/concepts" }, + { + "filePath": "/www/apps/resources/app/commerce-modules/order/custom-display-id/page.mdx", + "pathname": "/commerce-modules/order/custom-display-id" + }, { "filePath": "/www/apps/resources/app/commerce-modules/order/draft-orders/page.mdx", "pathname": "/commerce-modules/order/draft-orders" @@ -747,6 +751,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/examples/page.mdx", "pathname": "/examples" }, + { + "filePath": "/www/apps/resources/app/how-to-tutorials/how-to/admin/auth/page.mdx", + "pathname": "/how-to-tutorials/how-to/admin/auth" + }, { "filePath": "/www/apps/resources/app/how-to-tutorials/page.mdx", "pathname": "/how-to-tutorials" diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs index bef728c791..ce367fbc5d 100644 --- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs @@ -471,7 +471,7 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "isPathHref": true, "type": "category", "title": "Server Guides", - "autogenerate_tags": "auth+server", + "autogenerate_tags": "server+auth", "autogenerate_as_ref": true, "sort_sidebar": "alphabetize", "description": "Learn how to use the Auth Module in your customizations on the Medusa application server.", @@ -492,6 +492,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "title": "Create Auth Provider", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Custom Admin Authentication", + "path": "https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth", + "children": [] + }, { "loaded": true, "isPathHref": true, @@ -504,7 +512,7 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "loaded": true, "isPathHref": true, "type": "ref", - "title": "Implement Phone Authentication", + "title": "Phone Authentication", "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/phone-auth", "children": [] } @@ -6143,6 +6151,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "title": "Order Concepts", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/commerce-modules/order/custom-display-id", + "title": "Custom Order Display ID", + "children": [] + }, { "loaded": true, "isPathHref": true, @@ -16946,6 +16962,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "sort_sidebar": "alphabetize", "description": "Learn how to use the User Module in your customizations on the Medusa application server.", "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Custom Admin Authentication", + "path": "https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs index 2be93b78f9..07a1186b38 100644 --- a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs +++ b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs @@ -256,6 +256,7 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = { "autogenerate_tags": "howTo+admin", "autogenerate_as_ref": true, "sort_sidebar": "alphabetize", + "description": "These how-to guides help you customize the Medusa Admin dashboard with practical examples.", "children": [ { "loaded": true, @@ -384,6 +385,14 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = { ] } ] + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "title": "Custom Admin Authentication", + "path": "/how-to-tutorials/how-to/admin/auth", + "children": [] } ] } diff --git a/www/apps/resources/sidebars/auth.mjs b/www/apps/resources/sidebars/auth.mjs index f43f51cfc4..efdc1168a3 100644 --- a/www/apps/resources/sidebars/auth.mjs +++ b/www/apps/resources/sidebars/auth.mjs @@ -47,7 +47,7 @@ export const authSidebar = [ { type: "category", title: "Server Guides", - autogenerate_tags: "auth+server", + autogenerate_tags: "server+auth", autogenerate_as_ref: true, sort_sidebar: "alphabetize", description: diff --git a/www/apps/resources/sidebars/how-to-tutorials.mjs b/www/apps/resources/sidebars/how-to-tutorials.mjs index 51b317733b..cfe50f706f 100644 --- a/www/apps/resources/sidebars/how-to-tutorials.mjs +++ b/www/apps/resources/sidebars/how-to-tutorials.mjs @@ -34,6 +34,8 @@ export const howToTutorialsSidebar = [ autogenerate_tags: "howTo+admin", autogenerate_as_ref: true, sort_sidebar: "alphabetize", + description: + "These how-to guides help you customize the Medusa Admin dashboard with practical examples.", children: [ { type: "sidebar", @@ -60,6 +62,11 @@ export const howToTutorialsSidebar = [ }, ], }, + { + type: "link", + title: "Custom Admin Authentication", + path: "/how-to-tutorials/how-to/admin/auth", + }, ], }, ], diff --git a/www/apps/resources/sidebars/order-module.mjs b/www/apps/resources/sidebars/order-module.mjs index 9508591aeb..c185f3cf93 100644 --- a/www/apps/resources/sidebars/order-module.mjs +++ b/www/apps/resources/sidebars/order-module.mjs @@ -22,6 +22,11 @@ export const orderSidebar = [ path: "/commerce-modules/order/concepts", title: "Order Concepts", }, + { + type: "link", + path: "/commerce-modules/order/custom-display-id", + title: "Custom Order Display ID", + }, { type: "link", path: "/commerce-modules/order/promotion-adjustments", diff --git a/www/apps/user-guide/app/orders/exchanges/page.mdx b/www/apps/user-guide/app/orders/exchanges/page.mdx index a981e2f924..be339b40b0 100644 --- a/www/apps/user-guide/app/orders/exchanges/page.mdx +++ b/www/apps/user-guide/app/orders/exchanges/page.mdx @@ -57,6 +57,8 @@ To create an order exchange: - The shipping method's cost is added to the exchange total. To edit its cost: - Click the icon next to the Outbound Shipping total. - Enter the new cost in the input shown. + - If the order has promotions, you can enable the "Carry over promotions" toggle to apply the same promotions to the exchange's items. + - You can only carry over fixed promotions with `EACH` allocation type, or percentage promotions of `EACH` or `ACROSS` allocation types. Promotions are carried over to outbound items only. - If you want the customer to receive a notification that an exchange has been created, check the “Send notifications” toggle. 5. Once done, click on the "Confirm Exchange" button, then confirm the action by clicking the "Continue" button in the pop-up. diff --git a/www/apps/user-guide/app/promotions/create/page.mdx b/www/apps/user-guide/app/promotions/create/page.mdx index b8f65ec052..30b99cc1dd 100644 --- a/www/apps/user-guide/app/promotions/create/page.mdx +++ b/www/apps/user-guide/app/promotions/create/page.mdx @@ -95,6 +95,10 @@ If you chose the "Amount off Products" promotion type in the first section, fill - Set the Attribute to Product Category. - Set the Operator to In. - Set the Value to the Shirts product category. +10. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. 10. Once you're done, click Next and move on to the [next step](#step-3-campaign). ![What items will the promotion be applied to section](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898160/User%20Guide/Screenshot_2025-02-18_at_7.02.26_PM_izi1sv.png) @@ -129,7 +133,11 @@ If you chose the "Amount off Order" promotion type in the first section, fill ou ![Who can use this code form](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898239/User%20Guide/Screenshot_2025-02-18_at_7.03.46_PM_iawubi.png) 6. In the Promotion Value field, set the amount to be discounted from the order when the promotion is applied. The amount is in the currency you chose in the previous section. -7. Once you're done, click Next and move on to the [next step](#step-3-campaign). +7. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. +8. Once you're done, click Next and move on to the [next step](#step-3-campaign). ### c. Percentage off Product @@ -185,7 +193,11 @@ If you chose the "Percentage off Product" promotion type in the first section, f - Set the Attribute to Product Category. - Set the Operator to In. - Set the Value to the Shirts product category. -10. Once you're done, click Next and move on to the [next step](#step-3-campaign). +10. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. +11. Once you're done, click Next and move on to the [next step](#step-3-campaign). ![What items will the promotion be applied to section](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898386/User%20Guide/Screenshot_2025-02-18_at_7.06.14_PM_bdi5jv.png) @@ -218,7 +230,11 @@ If you chose the "Percentage off Order" promotion type in the first section, fil ![Who can use this code section](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898478/User%20Guide/Screenshot_2025-02-18_at_7.07.46_PM_eriyxm.png) 6. In the Promotion Value field, set the amount to be discounted from applicable products when the promotion is applied. The amount is in the currency you chose in the previous section. -7. Once you're done, click Next and move on to the [next step](#step-3-campaign). +7. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. +8. Once you're done, click Next and move on to the [next step](#step-3-campaign). ### e. Buy X Get Y @@ -281,7 +297,11 @@ If you chose the "Buy X Get Y" promotion type in the first section, fill out the - Set the Attribute to Product Category. - Set the Operator to "Not In". - Set the Value to the "Shirt" category. -7. Once you're done, click Next and move on to the [next step](#step-3-campaign). +7. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. +8. Once you're done, click Next and move on to the [next step](#step-3-campaign). ### f. Free Shipping @@ -324,7 +344,11 @@ If you chose the "Free Shipping" promotion type in the first section, fill out t - Set the Attribute to Shipping Option Type. - Set the Operator to In. - Set the Value to the "Express" shipping option type. -6. Once you're done, click Next and move on to the [next step](#step-3-campaign). +6. In the Usage Limit field, optionally set the maximum number of times this promotion can be used across all orders. + - For example, if you set this field to `10`, the promotion can be used a total of 10 times across all orders. After that, the promotion can't be used anymore. + - If left empty, the promotion can be used an unlimited number of times, or until the campaign's usage limit is reached if the promotion is part of a campaign. + - You can't update this field after creating the promotion. +7. Once you're done, click Next and move on to the [next step](#step-3-campaign). ![What shipping methods will the promotion be applied to section](https://res.cloudinary.com/dza7lstvk/image/upload/v1756368838/User%20Guide/CleanShot_2025-08-28_at_11.13.29_2x_rdcivr.png) diff --git a/www/apps/user-guide/app/promotions/manage/page.mdx b/www/apps/user-guide/app/promotions/manage/page.mdx index 7b402b77ac..651766b101 100644 --- a/www/apps/user-guide/app/promotions/manage/page.mdx +++ b/www/apps/user-guide/app/promotions/manage/page.mdx @@ -28,7 +28,7 @@ To view a promotion's details: This opens the promotion's details page. The sections in the details page may differ based on the promotion's type. -![Promotion Details](https://res.cloudinary.com/dza7lstvk/image/upload/v1739951000/User%20Guide/Screenshot_2025-02-19_at_9.42.23_AM_fjpgv7.png) +![Promotion Details](https://res.cloudinary.com/dza7lstvk/image/upload/v1764580476/User%20Guide/CleanShot_2025-12-01_at_11.03.26_2x_xxxa8m.png) ### Promotion Statuses @@ -65,6 +65,17 @@ A promotion's status is shown in the Promotions listing page, and in the promoti +### Promotion Limits + +In the summary section of the promotion's details page, you can see the usage limit of the promotion, if set during its creation. This is the maximum number of times the promotion can be used across all orders. + +A promotion's usage can also be limited by: + +1. The campaign's usage limit, if the promotion is part of a campaign. This is the maximum number of times all promotions in the campaign can be used across all orders. You can set this limit when [creating the campaign](../campaigns/page.mdx). +2. The per-customer usage limit, if the promotion is part of a campaign. This is the maximum number of times a single customer can use promotions in a campaign across all their orders. You can set this limit when [creating the campaign](../campaigns/page.mdx). + +Once any of the promotion's limits are reached, the promotion can't be used anymore. For example, if a promotion has a usage limit of 10 and is part of a campaign with a usage limit of 50, the promotion can only be used 10 times even if the campaign's limit hasn't been reached yet. + --- ## Edit Promotion's Details diff --git a/www/apps/user-guide/generated/edit-dates.mjs b/www/apps/user-guide/generated/edit-dates.mjs index 273d2357e7..5c9b636fa0 100644 --- a/www/apps/user-guide/generated/edit-dates.mjs +++ b/www/apps/user-guide/generated/edit-dates.mjs @@ -33,15 +33,15 @@ export const generatedEditDates = { "app/discounts/create/page.mdx": "2024-05-03T17:36:38+03:00", "app/orders/payments/page.mdx": "2025-10-09T07:31:59.781Z", "app/discounts/page.mdx": "2024-05-03T17:36:38+03:00", - "app/orders/exchanges/page.mdx": "2025-05-30T13:27:55.646Z", + "app/orders/exchanges/page.mdx": "2025-12-01T09:27:18.971Z", "app/products/create/page.mdx": "2025-05-30T13:29:24.876Z", "app/products/edit/page.mdx": "2025-10-30T11:24:51.589Z", "app/products/variants/page.mdx": "2025-10-30T11:26:37.612Z", "app/products/create/bundle/page.mdx": "2025-05-30T13:29:15.958Z", "app/products/create/multi-part/page.mdx": "2025-11-27T08:28:54.245Z", "app/promotions/campaigns/page.mdx": "2025-10-13T10:14:17.948Z", - "app/promotions/create/page.mdx": "2025-10-14T12:09:22.188Z", - "app/promotions/manage/page.mdx": "2025-10-14T12:09:22.188Z", + "app/promotions/create/page.mdx": "2025-12-01T09:31:05.267Z", + "app/promotions/manage/page.mdx": "2025-12-01T09:17:00.967Z", "app/promotions/page.mdx": "2025-05-30T13:30:08.538Z", "app/price-lists/create/page.mdx": "2025-05-30T13:28:41.126Z", "app/price-lists/manage/page.mdx": "2025-05-30T13:28:47.929Z", diff --git a/www/packages/tags/src/tags/auth.ts b/www/packages/tags/src/tags/auth.ts index e5599e5e32..90d61f13b9 100644 --- a/www/packages/tags/src/tags/auth.ts +++ b/www/packages/tags/src/tags/auth.ts @@ -7,6 +7,10 @@ export const auth = [ "title": "Reset Password", "path": "https://docs.medusajs.com/user-guide/reset-password" }, + { + "title": "How to Add Custom Authentication in Medusa Admin", + "path": "https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth" + }, { "title": "Implement Phone Authentication", "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/phone-auth" diff --git a/www/packages/tags/src/tags/server.ts b/www/packages/tags/src/tags/server.ts index e09c8866ae..84deb2a0df 100644 --- a/www/packages/tags/src/tags/server.ts +++ b/www/packages/tags/src/tags/server.ts @@ -55,6 +55,10 @@ export const server = [ "title": "Implement Quote Management", "path": "https://docs.medusajs.com/resources/examples/guides/quote-management" }, + { + "title": "Custom Admin Authentication", + "path": "https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth" + }, { "title": "Abandoned Cart Notification", "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart" diff --git a/www/packages/tags/src/tags/user.ts b/www/packages/tags/src/tags/user.ts index e1952dd92f..2d061d5e1e 100644 --- a/www/packages/tags/src/tags/user.ts +++ b/www/packages/tags/src/tags/user.ts @@ -15,6 +15,10 @@ export const user = [ "title": "Manage Users", "path": "https://docs.medusajs.com/user-guide/settings/users" }, + { + "title": "How to Add Custom Authentication in Medusa Admin", + "path": "https://docs.medusajs.com/resources/how-to-tutorials/how-to/admin/auth" + }, { "title": "createInviteStep", "path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/createInviteStep"