diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx
new file mode 100644
index 0000000000..edcf046994
--- /dev/null
+++ b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx
@@ -0,0 +1,728 @@
+---
+sidebar_label: "First-Purchase Discount"
+tags:
+ - name: cart
+ label: "Implement First-Purchase Discount"
+ - server
+ - nextjs
+ - tutorial
+ - name: customer
+ label: "Implement First-Purchase Discount"
+ - name: promotion
+ label: "Implement First-Purchase Discount"
+products:
+ - cart
+ - customer
+ - promotion
+---
+
+import { Github } from "@medusajs/icons"
+import { Prerequisites, WorkflowDiagram, CardList } from "docs-ui"
+
+export const metadata = {
+ title: `Implement First-Purchase Discount in Medusa`,
+}
+
+# {metadata.title}
+
+In this tutorial, you'll learn how to implement first-purchase discounts in Medusa.
+
+When you install a Medusa application, you get a fully-fledged commerce platform with a Framework for customization. The Medusa application's commerce features are built around [Commerce Modules](../../../commerce-modules/page.mdx), which are available out-of-the-box. These features include promotion and discount management features.
+
+The first-purchase discount feature encourages customers to sign up and make their first purchase by offering them a discount. In this tutorial, you'll learn how to implement this feature in Medusa.
+
+## Summary
+
+By following this tutorial, you'll learn how to:
+
+- Install and set up Medusa.
+- Apply a first-purchase discount to a customer's cart if they are a first-time customer.
+- Add custom validation to ensure the discount is only used by first-time customers.
+- Customize the Next.js Starter Storefront to display a pop-up encouraging first-time customers to sign up and receive a discount.
+
+You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer.
+
+
+
+
+
+---
+
+## Step 1: Install a Medusa Application
+
+
+
+Start by installing the Medusa application on your machine with the following command:
+
+```bash
+npx create-medusa-app@latest
+```
+
+First, you'll be asked for the project's name. Then, when prompted about installing the [Next.js Starter Storefront](../../../nextjs-starter/page.mdx), choose "Yes."
+
+Afterward, the installation process will start, which will install the Medusa application in a directory with your project's name and the Next.js Starter Storefront in a separate directory named `{project-name}-storefront`.
+
+
+
+The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](!docs!/learn/fundamentals/api-routes). Learn more in [Medusa's Architecture documentation](!docs!/learn/introduction/architecture).
+
+
+
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterward, you can log in with the new user and explore the dashboard.
+
+
+
+Check out the [troubleshooting guides](../../../troubleshooting/create-medusa-app-errors/page.mdx) for help.
+
+
+
+---
+
+## Step 2: Create a First-Purchase Promotion
+
+Before you apply the first-purchase discount or promotion to a customer's cart, you need to create the promotion that will be applied.
+
+Start your Medusa application with the following command:
+
+```bash npm2yarn
+npm run dev
+```
+
+Then, open the Medusa Admin dashboard at `http://localhost:9000/app` and log in with the user you created in the previous step.
+
+Next, click on the "Promotions" tab in the left sidebar, then click on the "Create Promotion" button to create a new promotion.
+
+You can customize the promotion based on your use case. For example, it can be a `10%` off the entire order, or a fixed amount off specific items.
+
+Make sure to set the promotion's code to `FIRST_PURCHASE`, as you'll be using this code in your Medusa customization. If you want to use a different code, make sure to update the code in the next steps accordingly.
+
+
+
+Refer to the [Create Promotions User Guide](!user-guide!/promotions/create) to learn how to create promotions in Medusa.
+
+
+
+Once you create and publish the promotion, you can proceed to the next steps.
+
+
+
+---
+
+## Step 3: Apply the First-Purchase Discount to Cart
+
+In this step, you'll customize the Medusa application to automatically apply the first-purchase promotion to a cart.
+
+To build this feature, you need to:
+
+- Create a [workflow](!docs!/learn/fundamentals/workflows) that implements the logic to apply the first-purchase promotion to a cart.
+- Execute the workflow in a [subscriber](!docs!/learn/fundamentals/events-and-subscribers) that is triggered when a cart is created, or when it's transferred to a customer.
+
+### a. Store the First-Purchase Promotion Code
+
+Since you'll refer to the first-purchase promotion code in multiple places, it's a good idea to store it as a constant in your Medusa application.
+
+So, create the file `src/constants.ts` with the following content:
+
+```ts title="src/constants.ts"
+export const FIRST_PURCHASE_PROMOTION_CODE = "FIRST_PURCHASE"
+```
+
+You'll reference this constant in the next steps.
+
+### b. Create the Workflow
+
+Next, you'll create the workflow that implements the logic to apply the first-purchase promotion to a cart.
+
+A [workflow](!docs!/learn/fundamentals/workflows) is a series of actions, called steps, that complete a task with rollback and retry mechanisms. In Medusa, you build commerce features in workflows, then execute them in other customizations, such as subscribers, scheduled jobs, and API routes.
+
+The workflow you'll build will have the following steps:
+
+
+
+Medusa provides all these steps in its `@medusajs/medusa/core-flows` package, so you can implement the workflow right away.
+
+To create the workflow, create the file `src/workflows/apply-first-purchase-promo.ts` with the following content:
+
+export const workflowHighlights = [
+ ["7", "cart_id", "Receive cart ID as input."],
+ ["14", "carts", "Retrieve the cart's details."],
+ ["23", "promotions", "Retrieve the first-purchase promotion's details."],
+ ["32", "when", "Check if the first-purchase promotion can be applied."],
+ ["42", "updateCartPromotionsStep", "Add the first-purchase promotion to the cart."],
+ ["51", "updatedCarts", "Retrieve the updated cart's details."],
+ ["59", "WorkflowResponse", "Return the updated cart's details."]
+]
+
+```ts title="src/workflows/apply-first-purchase-promo.ts" highlights={workflowHighlights}
+import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { updateCartPromotionsStep, useQueryGraphStep } from "@medusajs/medusa/core-flows"
+import { FIRST_PURCHASE_PROMOTION_CODE } from "../constants"
+import { PromotionActions } from "@medusajs/framework/utils"
+
+type WorkflowInput = {
+ cart_id: string
+}
+
+export const applyFirstPurchasePromoWorkflow = createWorkflow(
+ "apply-first-purchase-promo",
+ (input: WorkflowInput) => {
+ // @ts-ignore
+ const { data: carts } = useQueryGraphStep({
+ entity: "cart",
+ fields: ["promotions.*", "customer.*", "customer.orders.*"],
+ filters: {
+ id: input.cart_id
+ }
+ })
+
+ // @ts-ignore
+ const { data: promotions } = useQueryGraphStep({
+ entity: "promotion",
+ fields: ["code"],
+ filters: {
+ // @ts-ignore
+ code: FIRST_PURCHASE_PROMOTION_CODE
+ }
+ }).config({ name: "retrieve-promotions" })
+
+ when({
+ carts,
+ promotions
+ }, (data) => {
+ return data.promotions.length > 0 &&
+ !data.carts[0].promotions?.some((promo) => promo?.id === data.promotions[0].id) &&
+ data.carts[0].customer !== null &&
+ data.carts[0].customer.orders?.length === 0
+ })
+ .then(() => {
+ updateCartPromotionsStep({
+ id: carts[0].id,
+ promo_codes: [promotions[0].code!],
+ action: PromotionActions.ADD
+ })
+ })
+
+ // retrieve updated cart
+ // @ts-ignore
+ const { data: updatedCarts } = useQueryGraphStep({
+ entity: "cart",
+ fields: ["*", "promotions.*"],
+ filters: {
+ id: input.cart_id
+ }
+ }).config({ name: "retrieve-updated-cart" })
+
+ return new WorkflowResponse(updatedCarts[0])
+ }
+)
+```
+
+You create a workflow using `createWorkflow` from the Workflows SDK. It accepts the workflow's unique name as a first parameter.
+
+`createWorkflow` accepts as a second parameter a constructor function, which is the workflow's implementation. The function accepts as an input an object with the ID of the cart to apply the first-purchase promotion to.
+
+In the workflow's constructor function, you:
+
+- Retrieve the cart's details, including its promotions and customer, using the [useQueryGraphStep](/references/helper-steps/useQueryGraphStep).
+- Retrieve the details of the first-purchase promotion using the `useQueryGraphStep`.
+ - You pass the `FIRST_PURCHASE_PROMOTION_CODE` constant to the `filters` option to retrieve the promotion.
+- Use the [when-then](!docs!/learn/fundamentals/workflows/conditions) utility to only apply the promotion if the first-purchase promotion exists, the cart doesn't have the promotion, and the customer doesn't have any orders. `when` receives two parameters:
+ - An object to use in the condition function.
+ - A condition function that receives the first parameter object and returns a boolean indicating whether to execute the steps in the `then` block.
+- Retrieve the updated cart's details, including its promotions, using the `useQueryGraphStep` again.
+
+Finally, you return a `WorkflowResponse` with the updated cart's details.
+
+
+
+You can't perform data manipulation in a workflow's constructor function. Instead, the Workflows SDK includes utility functions like `when` to perform typical operations that require accessing data values. Learn more about workflow constraints in the [Workflow Constraints](!docs!/learn/fundamentals/workflows/constructor-constraints) documentation.
+
+
+
+### c. Create the Subscriber
+
+Next, you'll create a subscriber that executes the workflow when a cart is created or transferred to a customer.
+
+
+
+A cart can be transferred to a customer when they sign up or log in, or in B2B use cases.
+
+
+
+A [subscriber](!docs!/learn/fundamentals/events-and-subscribers) is an asynchronous function that listens to events to perform a task. In this case, you'll create a subscriber that listens to the `cart.created` and `cart.customer_transferred` events to execute the workflow.
+
+To create the subscriber, create the file `src/subscribers/apply-first-purchase.ts` with the following content:
+
+```ts title="src/subscribers/apply-first-purchase.ts"
+import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework"
+import { applyFirstPurchasePromoWorkflow } from "../workflows/apply-first-purchase-promo"
+
+export default async function cartCreatedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{
+ id: string
+}>) {
+ await applyFirstPurchasePromoWorkflow(container)
+ .run({
+ input: {
+ cart_id: data.id
+ }
+ })
+}
+
+export const config: SubscriberConfig = {
+ event: ["cart.created", "cart.customer_transferred"],
+}
+```
+
+A subscriber file must export:
+
+1. An asynchronous function, which is the subscriber that is executed when the event is emitted.
+2. A configuration object that holds the names of the events the subscriber listens to, which are `cart.created` and `cart.customer_transferred` in this case.
+
+The subscriber function receives an object as a parameter that has a `container` property, which is the [Medusa container](!docs!/learn/fundamentals/medusa-container). The Medusa container holds Framework and commerce tools that you can resolve and use in your customizations.
+
+In the subscriber function, you execute the `applyFirstPurchasePromoWorkflow` by invoking it, passing it the Medusa container, then calling its `run` method. You pass the `cart_id` from the event payload as an input to the workflow.
+
+### Test it Out
+
+You can now test the automatic application of the first-purchase promotion to a cart. To do that, you'll use the [Next.js Starter Storefront](../../../nextjs-starter/page.mdx) you installed in the first step.
+
+
+
+The Next.js Starter Storefront was installed in a separate directory from Medusa. The directory's name is `{your-project}-storefront`.
+
+So, if your Medusa application's directory is `medusa-first-promo`, you can find the storefront by going back to the parent directory and changing to the `medusa-first-promo-storefront` directory:
+
+```bash
+cd ../medusa-first-promo-storefront # change based on your project name
+```
+
+
+
+First, start the Medusa application with the following command:
+
+```bash npm2yarn badgeLabel="Medusa Application" badgeColor="green"
+npm run dev
+```
+
+Then, start the Next.js Starter Storefront with the following command:
+
+```bash npm2yarn badgeLabel="Storefront" badgeColor="blue"
+npm run dev
+```
+
+The storefront will run at `http://localhost:8000`. Open it in your browser and click on Account at the top right to register.
+
+After you register, add a product to the cart, then go to the cart page. You'll find that the `FIRST_PURCHASE` promotion has been applied to the cart automatically.
+
+
+
+---
+
+## Step 4: Validate First-Purchase Discount Usage
+
+You now automatically apply the first-purchase promotion to a cart, but any customer can use the promotion code at the moment.
+
+So, you need to add custom validation to ensure that the first-purchase promotion is only used by first-time customers.
+
+In this step, you'll customize Medusa's existing workflows to validate the first-purchase promotion usage. You can do that by consuming the [workflows' hooks](!docs!/learn/fundamentals/workflows/workflow-hooks). A workflow hook is a point in a workflow where you can inject custom functionality as a step function.
+
+You'll consume the hooks of the following workflows:
+
+- [updateCartPromotionsWorkflow](/references/medusa-workflows/updateCartPromotionsWorkflow): This workflow is used to add or remove promotions from a cart. You'll check that the customer is a first-time customer before allowing the promotion to be added.
+- [completeCartWorkflow](/references/medusa-workflows/completeCartWorkflow): This workflow is used to complete a cart and place an order. You'll validate that the first-purchase promotion is only used by first-time customers before allowing the order to be placed.
+
+### a. Consume `updateCartPromotionsWorkflow.validate` Hook
+
+You'll start by consuming the `validate` hook of the `updateCartPromotionsWorkflow`. This hook is called before any operations are performed in the workflow.
+
+To consume the hook, create the file `src/workflows/hooks/validate-promotion.ts` with the following content:
+
+export const validatePromotionHighlights = [
+ ["9", "hasFirstPurchasePromo", "Check if the first-purchase promotion is being applied."],
+ ["17", "", "Throw an error if the cart isn't associated with a customer."],
+ ["25", "customer", "Retrieve the customer associated with the cart."],
+ ["33", "", "Throw an error if the customer doesn't have an account or has previous orders."],
+]
+
+```ts title="src/workflows/hooks/validate-promotion.ts" highlights={validatePromotionHighlights}
+import {
+ updateCartPromotionsWorkflow,
+} from "@medusajs/medusa/core-flows"
+import { FIRST_PURCHASE_PROMOTION_CODE } from "../../constants"
+import { MedusaError } from "@medusajs/framework/utils"
+
+updateCartPromotionsWorkflow.hooks.validate(
+ (async ({ input, cart }, { container }) => {
+ const hasFirstPurchasePromo = input.promo_codes?.some(
+ (code) => code === FIRST_PURCHASE_PROMOTION_CODE
+ )
+
+ if (!hasFirstPurchasePromo) {
+ return
+ }
+
+ if (!cart.customer_id) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "First purchase discount can only be applied to carts with a customer"
+ )
+ }
+ const query = container.resolve("query")
+
+ const { data: [customer] } = await query.graph({
+ entity: "customer",
+ fields: ["orders.*", "has_account"],
+ filters: {
+ id: cart.customer_id
+ }
+ })
+
+ if (!customer.has_account || (customer?.orders?.length || 0) > 0) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "First purchase discount can only be applied to carts with no previous orders"
+ )
+ }
+ })
+)
+```
+
+You consume a workflow's hook by calling the `hooks` property of the workflow, then calling the hook you want to consume. In this case, you call the `validate` hook of the `updateCartPromotionsWorkflow`.
+
+The `validate` hook receives a step function as a parameter. The function receives two parameters:
+
+- The hook's input, which differs based on the workflow. In this case, it receives the following properties:
+ - `input`: The input of the `updateCartPromotionsWorkflow`, which includes the `promo_codes` to add or remove from the cart.
+ - `cart`: The cart being updated.
+- The hook or step context object. Most notably, it has a `container` property, which is the Medusa container.
+
+In the step function, you check if the `FIRST_PURCHASE_PROMOTION_CODE` is being applied to the cart. If so, you validate that:
+
+- The cart is associated with a customer.
+- The customer has an account.
+- The customer has no previous orders.
+
+If any of these validations fail, you throw a `MedusaError` with the appropriate error message. This will prevent the promotion from being applied to the cart.
+
+
+
+To retrieve the customer's details, you use [Query](!docs!/learn/fundamentals/module-links/query). Query allows you to retrieve data across modules in your Medusa application.
+
+
+
+### b. Consume `completeCartWorkflow.validate` Hook
+
+Next, you'll consume the `validate` hook of the `completeCartWorkflow`. This workflow is used to complete a cart and place an order. You'll validate that the first-purchase promotion is only used by first-time customers before allowing the order to be placed.
+
+In the same `src/workflows/hooks/validate-promotion.ts` file, add the following import at the top of the file:
+
+```ts title="src/workflows/hooks/validate-promotion.ts"
+import {
+ completeCartWorkflow
+} from "@medusajs/medusa/core-flows"
+```
+
+Then, consume the hook at the end of the file:
+
+export const validateCartCompletionHighlights = [
+ ["3", "hasFirstPurchasePromo", "Check if the first-purchase promotion is being applied."],
+ ["11", "", "Throw an error if the cart isn't associated with a customer."],
+ ["20", "customer", "Retrieve the customer associated with the cart."],
+ ["28", "", "Throw an error if the customer doesn't have an account or has previous orders."],
+]
+
+```ts title="src/workflows/hooks/validate-promotion.ts" highlights={validateCartCompletionHighlights}
+completeCartWorkflow.hooks.validate(
+ (async ({ input, cart }, { container }) => {
+ const hasFirstPurchasePromo = cart.promotions?.some(
+ (promo) => promo?.code === FIRST_PURCHASE_PROMOTION_CODE
+ )
+
+ if (!hasFirstPurchasePromo) {
+ return
+ }
+
+ if (!cart.customer_id) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "First purchase discount can only be applied to carts with a customer"
+ )
+ }
+
+ const query = container.resolve("query")
+
+ const { data: [customer] } = await query.graph({
+ entity: "customer",
+ fields: ["orders.*", "has_account"],
+ filters: {
+ id: cart.customer_id
+ }
+ })
+
+ if (!customer.has_account || (customer?.orders?.length || 0) > 0) {
+ throw new MedusaError(
+ MedusaError.Types.INVALID_DATA,
+ "First purchase discount can only be applied to carts with no previous orders"
+ )
+ }
+ })
+)
+```
+
+You consume the `validate` hook of the `completeCartWorkflow` in the same way as the previous hook. The step function receives the cart being completed as an input.
+
+In the step function, you check if the `FIRST_PURCHASE_PROMOTION_CODE` is applied to the cart. If so, you validate that:
+
+- The cart is associated with a customer.
+- The customer has an account.
+- The customer has no previous orders.
+
+If any of these validations fail, you throw a `MedusaError` with the appropriate error message. This will prevent the order from being placed if the first-purchase promotion is used by a customer who is not a first-time customer.
+
+### Test it Out
+
+To test the custom validation, start the Medusa application and the Next.js Starter Storefront as you did in the previous steps.
+
+Then, register a new customer in the storefront, and place an order. The first-purchase promotion will be applied to the cart automatically and the order will be placed successfully.
+
+Try to place another order with the same customer. The first-purchase promotion will not be automatically applied to the cart. If you also try to apply the first-purchase promotion manually, you'll receive an error message indicating that the promotion can only be applied to first-time customers.
+
+---
+
+## Step 5: Show Discount Pop-Up in Storefront
+
+The first-time purchase promotion is now fully functional. However, you need to inform first-time customers about the discount and encourage them to sign up.
+
+To do that, you'll customize the Next.js Starter Storefront to show a pop-up when a first-time customer visits the storefront.
+
+### a. Create the Pop-Up Component
+
+You'll first create the pop-up component that will be displayed to first-time customers.
+
+Create the file `src/modules/common/components/discount-popup/index.tsx` with the following content:
+
+```tsx title="src/modules/common/components/discount-popup/index.tsx" badgeLabel="Storefront" badgeColor="blue"
+"use client"
+
+import { Button, Heading, Text } from "@medusajs/ui"
+import Modal from "@modules/common/components/modal"
+import useToggleState from "@lib/hooks/use-toggle-state"
+import { useEffect } from "react"
+import LocalizedClientLink from "@modules/common/components/localized-client-link"
+
+const DISCOUNT_POPUP_KEY = "discount_popup_shown"
+
+const DiscountPopup = () => {
+ const { state, open, close } = useToggleState(false)
+
+ useEffect(() => {
+ // Check if the popup has been shown before
+ const hasBeenShown = localStorage.getItem(DISCOUNT_POPUP_KEY)
+
+ if (!hasBeenShown) {
+ open()
+ // Mark as shown
+ localStorage.setItem(DISCOUNT_POPUP_KEY, "true")
+ }
+ }, [open])
+
+ return (
+
+
+ {/* Decorative elements */}
+
+
+
+
+ {/* Sale tag */}
+
+ SAVE 10%
+
+
+
+ Limited Time Offer!
+
+
+
+
+
10%
+
OFF YOUR FIRST ORDER
+
+ %
+
+
+
+
+
+
+
+
+
+ Sign up now to receive an exclusive 10% discount on your first purchase. Join our community of satisfied customers!
+
+
+
+
+
+
+
+
+
+
+
+ *Discount applies to your first order only
+
+
+
+
+ )
+}
+
+export default DiscountPopup
+```
+
+This component uses the `Modal` component that is already available in the Next.js Starter Storefront. It displays a pop-up with a discount offer and two buttons: one to register and save the discount, and another to close the pop-up.
+
+The pop-up will only be shown to first-time customers. Once the pop-up is shown, a `discount_popup_shown` key is stored in the local storage to prevent it from being shown again.
+
+### b. Add the Pop-Up to Layout
+
+To ensure that the pop-up is displayed when the customer visits the storefront, you need to add the `DiscountPopup` component to the main layout of the Next.js Starter Storefront.
+
+In `src/app/[countryCode]/(main)/layout.tsx`, add the following import at the top of the file:
+
+```tsx title="src/app/[countryCode]/(main)/layout.tsx" badgeLabel="Storefront" badgeColor="blue"
+import DiscountPopup from "@modules/common/components/discount-popup"
+```
+
+Then, in the return statement of the `PageLayout` component, add the `DiscountPopup` component before rendering `props.children`:
+
+```tsx title="src/app/[countryCode]/(main)/layout.tsx" badgeLabel="Storefront" badgeColor="blue"
+<>
+ {/* ... */}
+ {!customer && }
+ {props.children}
+ {/* ... */}
+>
+```
+
+Notice that you only display the pop-up if the customer is not logged in. This way, the pop-up will only be shown to first-time customers.
+
+### c. Show Registration Form Before Login
+
+If you go to the `/account` page in the Next.js Starter Storefront as a guest customer, you'll see the login form. However, in this case, you want to show the registration form first instead.
+
+To change this behavior, in `src/modules/account/templates/login-template.tsx`, change the default value of `currentView` to `"register"`:
+
+```tsx title="src/modules/account/templates/login-template.tsx" badgeLabel="Storefront" badgeColor="blue"
+const [currentView, setCurrentView] = useState("register")
+```
+
+This way, when a guest customer visits the `/account` page, they will see the registration form instead of the login form.
+
+### Test it Out
+
+To test the pop-up, start the Medusa application and the Next.js Starter Storefront as you did in the previous steps.
+
+Then, open the storefront in your browser. If you're a first-time customer, you'll see the discount pop-up encouraging you to sign up and receive the first-purchase discount.
+
+
+
+If you don't see the pop-up, make sure that you're logged out.
+
+
+
+
+
+---
+
+## Next Steps
+
+You've now implemented the first-purchase discount feature in Medusa. You can add more features to build customer loyalty, such as a [loyalty points system](../loyalty-points/page.mdx) or [product reviews](../product-reviews/page.mdx).
+
+If you're new to Medusa, check out the [main documentation](!docs!/learn), where you'll get a more in-depth understanding of all the concepts you've used in this guide and more.
+
+To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](../../../commerce-modules/page.mdx).
+
+### Troubleshooting
+
+If you encounter issues during your development, check out the [troubleshooting guides](../../../troubleshooting/page.mdx).
+
+### Getting Help
+
+If you encounter issues not covered in the troubleshooting guides:
+
+1. Visit the [Medusa GitHub repository](https://github.com/medusajs/medusa) to report issues or ask questions.
+2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members.
diff --git a/www/apps/resources/app/infrastructure-modules/notification/page.mdx b/www/apps/resources/app/infrastructure-modules/notification/page.mdx
index a39a834872..9ffbe591bf 100644
--- a/www/apps/resources/app/infrastructure-modules/notification/page.mdx
+++ b/www/apps/resources/app/infrastructure-modules/notification/page.mdx
@@ -86,8 +86,41 @@ Medusa provides other Notification Modules that actually send notifications, suc
variant: "green",
children: "For Production"
}
+ },
+ {
+ title: "Mailchimp",
+ href: "/integrations/guides/mailchimp",
+ badge: {
+ variant: "blue",
+ children: "Tutorial",
+ }
+ },
+ {
+ title: "Resend",
+ href: "/integrations/guides/resend",
+ badge: {
+ variant: "blue",
+ children: "Tutorial",
+ }
+ },
+ {
+ title: "Slack",
+ href: "/integrations/guides/slack",
+ badge: {
+ variant: "blue",
+ children: "Tutorial",
+ }
+ },
+ {
+ title: "Twilio SMS",
+ href: "/how-to-tutorials/tutorials/phone-auth#step-3-integrate-twilio-sms",
+ badge: {
+ variant: "blue",
+ children: "Tutorial",
+ }
}
]}
+ itemsPerRow={2}
/>
---
diff --git a/www/apps/resources/app/integrations/page.mdx b/www/apps/resources/app/integrations/page.mdx
index 0da065f847..aacc9b0e8d 100644
--- a/www/apps/resources/app/integrations/page.mdx
+++ b/www/apps/resources/app/integrations/page.mdx
@@ -242,6 +242,7 @@ A Notification Module Provider sends messages to users and customers in your Med
}
]}
className="mb-1"
+ itemsPerRow={2}
/>
Learn how to integrate a third-party notification provider in the [Create Notification Module Provider](/references/notification-provider-module) documentation.
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 77b1332ea9..56575223d0 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -104,7 +104,7 @@ export const generatedEditDates = {
"app/deployment/medusa-application/railway/page.mdx": "2025-04-17T08:28:58.981Z",
"app/deployment/storefront/vercel/page.mdx": "2025-05-20T07:51:40.712Z",
"app/deployment/page.mdx": "2025-06-24T08:50:10.114Z",
- "app/integrations/page.mdx": "2025-05-26T14:47:43.842Z",
+ "app/integrations/page.mdx": "2025-06-25T10:48:35.928Z",
"app/medusa-cli/page.mdx": "2024-08-28T11:25:32.382Z",
"app/medusa-container-resources/page.mdx": "2025-04-17T08:48:10.255Z",
"app/medusa-workflows-reference/page.mdx": "2025-01-20T08:21:29.962Z",
@@ -215,7 +215,7 @@ export const generatedEditDates = {
"app/infrastructure-modules/event/page.mdx": "2025-04-17T08:29:00.488Z",
"app/infrastructure-modules/cache/create/page.mdx": "2025-03-27T14:53:13.309Z",
"app/admin-widget-injection-zones/page.mdx": "2024-12-24T08:48:36.154Z",
- "app/infrastructure-modules/notification/page.mdx": "2025-04-17T08:29:00.814Z",
+ "app/infrastructure-modules/notification/page.mdx": "2025-06-25T10:48:23.838Z",
"app/infrastructure-modules/event/create/page.mdx": "2025-03-27T14:53:13.309Z",
"references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityValidationStep/page.mdx": "2024-08-20T00:10:58.913Z",
"references/core_flows/Order/functions/core_flows.Order.orderEditUpdateItemQuantityWorkflow/page.mdx": "2024-08-20T00:10:58.949Z",
@@ -6547,6 +6547,7 @@ export const generatedEditDates = {
"app/integrations/guides/slack/page.mdx": "2025-06-03T09:37:42.528Z",
"app/integrations/guides/sentry/page.mdx": "2025-06-16T10:11:29.955Z",
"app/integrations/guides/mailchimp/page.mdx": "2025-06-24T08:08:35.034Z",
+ "app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx": "2025-06-25T10:44:38.113Z",
"references/types/CommonTypes/interfaces/types.CommonTypes.CookieOptions/page.mdx": "2025-06-25T10:11:37.088Z",
"references/types/types/types.RemoteQueryFilterOperators/page.mdx": "2025-06-25T10:11:39.849Z",
"references/types/ModulesSdkTypes/types/types.ModulesSdkTypes.InternalRemoteQueryFilters/page.mdx": "2025-06-25T10:11:39.832Z",
diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs
index 073956ce2a..ea734cad91 100644
--- a/www/apps/resources/generated/files-map.mjs
+++ b/www/apps/resources/generated/files-map.mjs
@@ -739,6 +739,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/abandoned-cart/page.mdx",
"pathname": "/how-to-tutorials/tutorials/abandoned-cart"
},
+ {
+ "filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx",
+ "pathname": "/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/loyalty-points/page.mdx",
"pathname": "/how-to-tutorials/tutorials/loyalty-points"
diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
index 6642fb6298..482700981e 100644
--- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
+++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs
@@ -1103,6 +1103,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"path": "https://docs.medusajs.com/resources/examples/guides/custom-item-price",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
@@ -2429,6 +2437,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"title": "Extend Module",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
@@ -13116,6 +13132,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
"title": "Extend Module",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts",
+ "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 229b224a30..71810af941 100644
--- a/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs
+++ b/www/apps/resources/generated/generated-how-to-tutorials-sidebar.mjs
@@ -436,6 +436,15 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = {
}
]
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "title": "First-Purchase Discounts",
+ "path": "/how-to-tutorials/tutorials/first-purchase-discounts",
+ "description": "Learn how to implement first-purchase discounts in your Medusa store.",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
diff --git a/www/apps/resources/generated/generated-tools-sidebar.mjs b/www/apps/resources/generated/generated-tools-sidebar.mjs
index 79a5715f49..eeefc5ee8e 100644
--- a/www/apps/resources/generated/generated-tools-sidebar.mjs
+++ b/www/apps/resources/generated/generated-tools-sidebar.mjs
@@ -803,6 +803,14 @@ const generatedgeneratedToolsSidebarSidebar = {
"autogenerate_as_ref": true,
"sort_sidebar": "alphabetize",
"children": [
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "ref",
+ "title": "First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
diff --git a/www/apps/resources/sidebars/how-to-tutorials.mjs b/www/apps/resources/sidebars/how-to-tutorials.mjs
index 9de4b10a2b..15c7eb8ca7 100644
--- a/www/apps/resources/sidebars/how-to-tutorials.mjs
+++ b/www/apps/resources/sidebars/how-to-tutorials.mjs
@@ -100,6 +100,13 @@ While tutorials show you a specific use case, they also help you understand how
description:
"Learn how to use prices from external systems for products.",
},
+ {
+ type: "link",
+ title: "First-Purchase Discounts",
+ path: "/how-to-tutorials/tutorials/first-purchase-discounts",
+ description:
+ "Learn how to implement first-purchase discounts in your Medusa store.",
+ },
{
type: "link",
title: "Loyalty Points System",
diff --git a/www/packages/tags/src/tags/cart.ts b/www/packages/tags/src/tags/cart.ts
index 549120c00d..6b67946a9d 100644
--- a/www/packages/tags/src/tags/cart.ts
+++ b/www/packages/tags/src/tags/cart.ts
@@ -15,6 +15,10 @@ export const cart = [
"title": "Send Abandoned Cart Notification",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart"
},
+ {
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Implement Loyalty Points",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
@@ -183,10 +187,6 @@ export const cart = [
"title": "deleteLineItemsWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteLineItemsWorkflow"
},
- {
- "title": "processPaymentWorkflow",
- "path": "https://docs.medusajs.com/resources/references/medusa-workflows/processPaymentWorkflow"
- },
{
"title": "cart",
"path": "https://docs.medusajs.com/resources/references/js-sdk/store/cart"
diff --git a/www/packages/tags/src/tags/customer.ts b/www/packages/tags/src/tags/customer.ts
index 8ba01bbf9a..6f93f6eceb 100644
--- a/www/packages/tags/src/tags/customer.ts
+++ b/www/packages/tags/src/tags/customer.ts
@@ -15,6 +15,10 @@ export const customer = [
"title": "Extend Customer",
"path": "https://docs.medusajs.com/resources/commerce-modules/customer/extend"
},
+ {
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Implement Loyalty Points",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
diff --git a/www/packages/tags/src/tags/inventory.ts b/www/packages/tags/src/tags/inventory.ts
index 8940420be9..0b970a5c36 100644
--- a/www/packages/tags/src/tags/inventory.ts
+++ b/www/packages/tags/src/tags/inventory.ts
@@ -171,10 +171,6 @@ export const inventory = [
"title": "orderExchangeAddNewItemWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/orderExchangeAddNewItemWorkflow"
},
- {
- "title": "processPaymentWorkflow",
- "path": "https://docs.medusajs.com/resources/references/medusa-workflows/processPaymentWorkflow"
- },
{
"title": "batchProductVariantsWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/batchProductVariantsWorkflow"
diff --git a/www/packages/tags/src/tags/link.ts b/www/packages/tags/src/tags/link.ts
index 981439aeef..41f0dac9ac 100644
--- a/www/packages/tags/src/tags/link.ts
+++ b/www/packages/tags/src/tags/link.ts
@@ -95,6 +95,10 @@ export const link = [
"title": "confirmDraftOrderEditWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/confirmDraftOrderEditWorkflow"
},
+ {
+ "title": "deleteDraftOrdersWorkflow",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteDraftOrdersWorkflow"
+ },
{
"title": "requestDraftOrderEditWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/requestDraftOrderEditWorkflow"
@@ -155,10 +159,6 @@ export const link = [
"title": "markPaymentCollectionAsPaid",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/markPaymentCollectionAsPaid"
},
- {
- "title": "processPaymentWorkflow",
- "path": "https://docs.medusajs.com/resources/references/medusa-workflows/processPaymentWorkflow"
- },
{
"title": "createPaymentSessionsWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/createPaymentSessionsWorkflow"
diff --git a/www/packages/tags/src/tags/locking.ts b/www/packages/tags/src/tags/locking.ts
index 84e6b2d65b..99c875a8ba 100644
--- a/www/packages/tags/src/tags/locking.ts
+++ b/www/packages/tags/src/tags/locking.ts
@@ -39,10 +39,6 @@ export const locking = [
"title": "createOrderFulfillmentWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/createOrderFulfillmentWorkflow"
},
- {
- "title": "processPaymentWorkflow",
- "path": "https://docs.medusajs.com/resources/references/medusa-workflows/processPaymentWorkflow"
- },
{
"title": "createReservationsStep",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/createReservationsStep"
diff --git a/www/packages/tags/src/tags/nextjs.ts b/www/packages/tags/src/tags/nextjs.ts
index 32e7ad357d..29a862e127 100644
--- a/www/packages/tags/src/tags/nextjs.ts
+++ b/www/packages/tags/src/tags/nextjs.ts
@@ -1,4 +1,8 @@
export const nextjs = [
+ {
+ "title": "First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Saved Payment Methods",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/saved-payment-methods"
diff --git a/www/packages/tags/src/tags/notification.ts b/www/packages/tags/src/tags/notification.ts
index 103d1e93de..2b3b0211ef 100644
--- a/www/packages/tags/src/tags/notification.ts
+++ b/www/packages/tags/src/tags/notification.ts
@@ -11,6 +11,10 @@ export const notification = [
"title": "Send Notification",
"path": "https://docs.medusajs.com/resources/infrastructure-modules/notification/send-notification"
},
+ {
+ "title": "Integrate Mailchimp",
+ "path": "https://docs.medusajs.com/resources/integrations/guides/mailchimp"
+ },
{
"title": "Integrate Slack",
"path": "https://docs.medusajs.com/resources/integrations/guides/slack"
diff --git a/www/packages/tags/src/tags/order.ts b/www/packages/tags/src/tags/order.ts
index 7f789a0ca5..7108ec5372 100644
--- a/www/packages/tags/src/tags/order.ts
+++ b/www/packages/tags/src/tags/order.ts
@@ -67,6 +67,10 @@ export const order = [
"title": "refundPaymentAndRecreatePaymentSessionWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/refundPaymentAndRecreatePaymentSessionWorkflow"
},
+ {
+ "title": "deleteDraftOrdersStep",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/deleteDraftOrdersStep"
+ },
{
"title": "addDraftOrderItemsWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/addDraftOrderItemsWorkflow"
@@ -91,6 +95,10 @@ export const order = [
"title": "confirmDraftOrderEditWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/confirmDraftOrderEditWorkflow"
},
+ {
+ "title": "deleteDraftOrdersWorkflow",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteDraftOrdersWorkflow"
+ },
{
"title": "removeDraftOrderActionItemWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/removeDraftOrderActionItemWorkflow"
diff --git a/www/packages/tags/src/tags/promotion.ts b/www/packages/tags/src/tags/promotion.ts
index 167e2529fe..d3ec32f5dc 100644
--- a/www/packages/tags/src/tags/promotion.ts
+++ b/www/packages/tags/src/tags/promotion.ts
@@ -19,6 +19,10 @@ export const promotion = [
"title": "Extend Promotion",
"path": "https://docs.medusajs.com/resources/commerce-modules/promotion/extend"
},
+ {
+ "title": "Implement First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Implement Loyalty Points",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
diff --git a/www/packages/tags/src/tags/query.ts b/www/packages/tags/src/tags/query.ts
index acdfa0a1f3..9a125c31fb 100644
--- a/www/packages/tags/src/tags/query.ts
+++ b/www/packages/tags/src/tags/query.ts
@@ -55,6 +55,10 @@ export const query = [
"title": "useQueryGraphStep",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep"
},
+ {
+ "title": "deleteDraftOrdersWorkflow",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteDraftOrdersWorkflow"
+ },
{
"title": "calculateShippingOptionsPricesWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/calculateShippingOptionsPricesWorkflow"
diff --git a/www/packages/tags/src/tags/server.ts b/www/packages/tags/src/tags/server.ts
index f00521252a..10a28a6dbf 100644
--- a/www/packages/tags/src/tags/server.ts
+++ b/www/packages/tags/src/tags/server.ts
@@ -47,6 +47,10 @@ export const server = [
"title": "Abandoned Cart Notification",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart"
},
+ {
+ "title": "First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Loyalty Points",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
@@ -87,6 +91,10 @@ export const server = [
"title": "Integrate Contentful",
"path": "https://docs.medusajs.com/resources/integrations/guides/contentful"
},
+ {
+ "title": "Integrate Mailchimp",
+ "path": "https://docs.medusajs.com/resources/integrations/guides/mailchimp"
+ },
{
"title": "Integrate Segment",
"path": "https://docs.medusajs.com/resources/integrations/guides/segment"
diff --git a/www/packages/tags/src/tags/step.ts b/www/packages/tags/src/tags/step.ts
index 7201494076..c474f824c2 100644
--- a/www/packages/tags/src/tags/step.ts
+++ b/www/packages/tags/src/tags/step.ts
@@ -255,6 +255,10 @@ export const step = [
"title": "createDefaultStoreStep",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/createDefaultStoreStep"
},
+ {
+ "title": "deleteDraftOrdersStep",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/deleteDraftOrdersStep"
+ },
{
"title": "validateDraftOrderStep",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/validateDraftOrderStep"
diff --git a/www/packages/tags/src/tags/tutorial.ts b/www/packages/tags/src/tags/tutorial.ts
index 49100efcf8..09ff51f0eb 100644
--- a/www/packages/tags/src/tags/tutorial.ts
+++ b/www/packages/tags/src/tags/tutorial.ts
@@ -27,6 +27,10 @@ export const tutorial = [
"title": "Abandoned Cart Notification",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/abandoned-cart"
},
+ {
+ "title": "First-Purchase Discount",
+ "path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/first-purchase-discounts"
+ },
{
"title": "Loyalty Points",
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
@@ -51,6 +55,10 @@ export const tutorial = [
"title": "Integrate Contentful",
"path": "https://docs.medusajs.com/resources/integrations/guides/contentful"
},
+ {
+ "title": "Integrate Mailchimp",
+ "path": "https://docs.medusajs.com/resources/integrations/guides/mailchimp"
+ },
{
"title": "Integrate Segment",
"path": "https://docs.medusajs.com/resources/integrations/guides/segment"
diff --git a/www/packages/tags/src/tags/workflow.ts b/www/packages/tags/src/tags/workflow.ts
index d3e87620e2..fe1b3099b6 100644
--- a/www/packages/tags/src/tags/workflow.ts
+++ b/www/packages/tags/src/tags/workflow.ts
@@ -203,6 +203,10 @@ export const workflow = [
"title": "convertDraftOrderWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/convertDraftOrderWorkflow"
},
+ {
+ "title": "deleteDraftOrdersWorkflow",
+ "path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteDraftOrdersWorkflow"
+ },
{
"title": "removeDraftOrderActionItemWorkflow",
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/removeDraftOrderActionItemWorkflow"