You are a typescript and medusajs professional. Create a trigger when a user reset a password the token should be printed to console (console.log).

Events reference:
user.password_reset
	

Triggered when a user requests to reset their password.

Object of the following format:

{
  email, // string email of user requesting to reset their password
  token // token create to reset the password
}
Events and Subscribers Overview:
Events

In this document, you’ll learn what events are and why they’re useful in Medusa.
Overview

Events are used in Medusa to inform different parts of the commerce ecosystem that this event occurred. For example, when an order is placed, the order.placed event is triggered, which informs notification services like SendGrid to send a confirmation email to the customer.
Tip

If you want to implement order confirmation emails, you can check this step-by-step guide.

The events system in Medusa is built on a publish/subscribe architecture. The Medusa core publish an event when an action takes place, and modules, plugins, or other forms of customizations can subscribe to that event. Subscribers can then perform a task asynchronously when the event is triggered.

Although the core implements the main logic behind the events system, you’ll need to use an event module that takes care of the publish/subscribe functionality such as subscribing and emitting events. Medusa provides modules that you can use both for development and production, including Redis and Local modules.
Database Transactions

Transactions in Medusa ensure Atomicity, Consistency, Isolation, and Durability (ACID) guarantees for operations in the Medusa core.

In many cases, services typically update resources in the database and emit an event within a transactional operation. To ensure that these events don't cause data inconsistencies (for example, a plugin subscribes to an event to contact a third-party service, but the transaction fails) the concept of a staged job is introduced.

Instead of events being processed immediately, they're stored in the database as a staged job until they're ready. In other words, until the transaction has succeeded.

This rather complex logic is abstracted away from the consumers of the EventBusService, but here's an example of the flow when an API request is made:

    API request starts.
    Transaction is initiated.
    Service layer performs some logic.
    Events are emitted and stored in the database for eventual processing.
    Transaction is committed.
    API request ends.
    Events in the database become visible.

To pull staged jobs from the database, a separate enqueuer polls the database every three seconds to discover new visible jobs. These jobs are then added to the queue and processed as described in the Processing section earlier.
Emitting Events

You can emit events in Medusa using the EventBusService. For example:

this.eventBusService.emit("custom-event", {
  // attach any data to the event
})

You can also emit more than one event:

this.eventBusService.emit([
  {
    eventName: "custom-event-1",
    data: {
      // attach any data to the event
    },
  },
  {
    eventName: "custom-event-2",
    data: {
      // attach any data to the event
    },
  },
])

Available Modules
Medusa’s default starter project comes with the local event module (@medusajs/event-bus-local). For production environments, it’s recommended to use the Redis event module package (@medusajs/event-bus-redis) that you can install.
Subscribers

In this document, you'll learn what Subscribers are in Medusa.
What are Subscribers

Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the order.placed event and send the email when the event is emitted.

Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.

Custom subscribers are TypeScript or JavaScript files in your project's src/subscribers directory. Subscriber files must default export a handler function and export a configuration object.

Whenever an event is emitted, the subscriber’s handler function is executed. The handler function receives as a parameter an object that includes the data payload, among other parameters. For example, if an order is placed, the order.placed event is emitted and all the handlers receive the order ID in the data object.
Example Use Cases

Subscribers are useful in many use cases, including:

    Send a confirmation email to the customer when they place an order by subscribing to the order.placed event.
    Automatically assign new customers to a customer group by subscribing to the customer.created.
 Handle custom events that you emit

Create Events Module

In this document, you’ll learn how to create an events module.
Overview

Medusa provides ready-made modules for events, including the local and Redis modules. If you prefer another technology used for managing events, you can build a module locally and use it in your Medusa backend. You can also publish to NPM and reuse it across multiple Medusa backend instances.

In this document, you’ll learn how to build your own Medusa events module, mainly focusing on creating the event bus service and the available methods you need to implement within your module.
(Optional) Step 0: Prepare Module Directory

Before you start implementing your module, it's recommended to prepare the directory or project holding your custom implementation.

You can refer to the Project Preparation step in the Create Module documentation to learn how to do that.
Step 1: Create the Service

Create the file services/event-bus-custom.ts which will hold your event bus service. Note that the name of the file is recommended to be in the format event-bus-<service_name> where <service_name> is the name of the service you’re integrating. For example, event-bus-redis.

Add the following content to the file:
services/event-bus-custom.ts

import { EmitData, EventBusTypes } from "@medusajs/types"
import { AbstractEventBusModuleService } from "@medusajs/utils"

class CustomEventBus extends AbstractEventBusModuleService {
  async emit<T>(
    eventName: string, 
    data: T, 
    options: Record<string, unknown>
  ): Promise<void>;
  async emit<T>(data: EmitData<T>[]): Promise<void>;
  async emit(
    eventName: unknown, 
    data?: unknown, 
    options?: unknown
  ): Promise<void> {
    throw new Error("Method not implemented.")
  }
}

export default CustomEventBus

This creates the class CustomEventBus that implements the AbstractEventBusModuleService class imported from @medusajs/utils. Feel free to rename the class to what’s relevant for your event bus service.

In the class you must implement the emit method. You can optionally implement the subscribe and unsubscribe methods.
Step 2: Implement Methods
Note About the eventToSubscribersMap Property

The AbstractEventBusModuleService implements two methods for handling subscription: subscribe and unsubscribe. In these methods, the subscribed handler functions are managed within a class property eventToSubscribersMap, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler functions.

In your custom implementation, you can use this property to manage the subscribed handler functions. For example, you can get the subscribers of a method using the get method of the map:

const eventSubscribers = 
  this.eventToSubscribersMap.get(eventName) || []

Alternatively, you can implement custom logic for the subscribe and unsubscribe events, which is explained later in this guide.
constructor

The constructor method of a service allows you to prepare any third-party client or service necessary to be used in other methods. It also allows you to get access to the module’s options which are typically defined in medusa-config.js, and to other services and resources in the Medusa backend using dependency injection.

Here’s an example of how you can use the constructor to store the options of your module:
services/event-bus-custom.ts

class CustomEventBus extends AbstractEventBusModuleService {
  protected readonly moduleOptions: Record<string, any>

  constructor({
    // inject resources from the Medusa backend
    // for example, you can inject the logger
    logger,
  }, options) {
    super(...arguments)
    this.moduleOptions = options
  }

  // ...
}

emit

The emit method is used to push an event from Medusa into your messaging system. Typically, the subscribers to that event would then pick up the message and execute their asynchronous tasks.

The emit method has two different signatures:

    The first signature accepts three parameters. The first parameter is eventName being a required string indicating the name of the event to trigger. The second parameter is data being the optional data to send to subscribers of that event. The third optional parameter options which can be used to pass options specific to the event bus.
    The second signature accepts one parameter, which is an array of objects having three properties: eventName, data, and options. These are the same as the parameters that can be passed in the first signature. This signature allows emitting more than one event.

The options parameter depends on the event bus integration. For example, the Redis event bus accepts the following options:
services/event-bus-custom.ts

type JobData<T> = {
  eventName: string
  data: T
  completedSubscriberIds?: string[] | undefined
}

You can implement your method in a way that supports both signatures by checking the type of the first input. For example:
services/event-bus-custom.ts

class CustomEventBus extends AbstractEventBusModuleService {
  // ...
  async emit<T>(
    eventName: string, 
    data: T, 
    options: Record<string, unknown>
  ): Promise<void>;
  async emit<T>(data: EmitData<T>[]): Promise<void>;
  async emit<T>(
    eventOrData: string | EmitData<T>[], 
    data?: T, 
    options: Record<string, unknown> = {}
  ): Promise<void> {
    const isBulkEmit = Array.isArray(eventOrData)
    
    // emit event
  }
}

(optional) subscribe

As mentioned earlier, this method is already implemented in the AbstractEventBusModuleService class. This section explains how you can implement your custom subscribe logic if necessary.

The subscribe method attaches a handler function to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.

The subscribe method accepts three parameters:

    The first parameter eventName is a required string. It indicates which event the handler function is subscribing to.
    The second parameter subscriber is a required function that performs an action when the event is triggered.
    The third parameter context is an optional object that has the property subscriberId. Subscriber IDs are useful to differentiate between handler functions when retrying a failed method. It’s also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the subscribe method.

The implementation of this method depends on the service you’re using for the event bus:
services/event-bus-custom.ts

class CustomEventBus extends AbstractEventBusModuleService {
  // ...
  subscribe(
    eventName: string | symbol, 
    subscriber: EventBusTypes.Subscriber, 
    context?: EventBusTypes.SubscriberContext): this {
    // TODO implement subscription
  }
}

(optional) unsubscribe

As mentioned earlier, this method is already implemented in the AbstractEventBusModuleService class. This section explains how you can implement your custom unsubscribe logic if necessary.

The unsubscribe method is used to unsubscribe a handler function from an event.

The unsubscribe method accepts three parameters:

    The first parameter eventName is a required string. It indicates which event the handler function is unsubscribing from.
    The second parameter subscriber is a required function that was initially subscribed to the event.
    The third parameter context is an optional object that has the property subscriberId. It can be used to specify the ID of the subscriber to unsubscribe.

The implementation of this method depends on the service you’re using for the event bus:
services/event-bus-custom.ts

class CustomEventBus extends AbstractEventBusModuleService {
  // ...
  unsubscribe(
    eventName: string | symbol, 
    subscriber: EventBusTypes.Subscriber, 
    context?: EventBusTypes.SubscriberContext): this {
    // TODO implement subscription
  }
}

Step 3: Export the Service

After implementing the event bus service, you must export it so that the Medusa backend can use it.

Create the file index.ts with the following content:
services/event-bus-custom.ts

import { ModuleExports } from "@medusajs/modules-sdk"

import { CustomEventBus } from "./services"

const service = CustomEventBus

const moduleDefinition: ModuleExports = {
  service,
}

export default moduleDefinition

This exports a module definition, which requires at least a service. If you named your service something other than CustomEventBus, make sure to replace it with that.

You can learn more about what other properties you can export in your module definition in the Create a Module documentation.
Step 4: Test your Module

You can test your module in the Medusa backend by referencing it in the configurations.

To do that, add the module to the exported configuration in medusa-config.js as follows:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    eventBus: {
        resolve: "path/to/custom-module", 
        options: { 
          // any necessary options
        },
      },
    },
}

Make sure to replace the path/to/custom-module with a relative path from your Medusa backend to your module. You can learn more about module reference in the Create Module documentation.

You can also add any necessary options to the module.


How to Create a Subscriber

In this document, you’ll learn how to create a Subscriber in Medusa that listens to events to perform an action.
Tip

v1.18 of @medusajs/medusa introduced a new approach to create a subscriber. If you're looking for the old guide, you can find it here. However, it's highly recommended you follow this new approach, as the old one is deprecated.
Implementation

A subscriber is a TypeScript or JavaScript file that is created under src/subscribers. It can be created under subdirectories of src/subscribers as well. For example, you can place all subscribers to product events under the src/subscribers/products directory.

The subscriber file exports a default handler function, and the subscriber's configurations.

For example:
src/subscribers/product-update-handler.ts

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs, 
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs<Record<string, any>>) {
  const productService: ProductService = container.resolve(
    "productService"
  )

  const { id } = data

  const product = await productService.retrieve(id)

  // do something with the product...
}

export const config: SubscriberConfig = {
  event: ProductService.Events.UPDATED,
  context: {
    subscriberId: "product-update-handler",
  },
}

Subscriber Configuration

The exported configuration object of type SubscriberConfig must include the following properties:

    event: A string or an array of strings, each being the name of the event that the subscriber handler function listens to.
    context: An object that defines the context of the subscriber. It can accept any properties along with the subscriberId property. Learn more about the subscriber ID and the context object in this section.

Subscriber Handler Function

The default-export of the subscriber file is a handler function that is executed when the events specified in the exported configuration is triggered.

The function accepts a parameter of type SubscriberArgs, which has the following properties:

    data: The data payload of the emitted event. Its type is different for each event. So, make sure to check the events reference for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to SubscriberArgs, for example, Record<string, string>.
    eventName: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
    container: The dependency container that allows you to resolve Medusa resources, such as services.
    pluginOptions: When the subscriber is created within a plugin, this object holds the plugin's options defined in the Medusa configurations.

Context with Subscriber ID

The context property of the subscriber configuration object is passed to the eventBusService. You can pass the subscriberId and any custom data in it.
Note

The subscriber ID is useful when there is more than one handler function attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler functions when retrying a failed one, avoiding retrying all subscribers which can lead to data inconsistencies or general unwanted behavior in your system.
Inferred Subscriber ID

If you don't pass a subscriber ID to the subscriber configurations, the name of the subscriber function is used as the subscriber ID. If the subscriber function is an anonymous function, the name of the subscriber file is used instead.
Caveats for Local Event Bus

If you use the event-bus-local as your event bus service, note the following:

    The subscriberId passed in the context is overwritten to a random ID when using event-bus-local. So, setting the subscriber ID in the context won't have any effect in this case.
    The eventName passed to the handler function will be undefined when using event-bus-local as it doesn't pass the event name properly.

Note

While the local event bus is a good option for development, it's highly recommended to use the Redis Event Module in production.
Retrieve Medusa Configurations

Within your subscriber, you may need to access the Medusa configuration exported from medusa-config.js. To do that, you can access configModule using dependency injection.

For example:
src/subscribers/product-update-handler.ts

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs,
  type ConfigModule, 
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs) {
  const configModule: ConfigModule = container.resolve(
    "configModule"
  )
  
  // ...
}

export const config: SubscriberConfig = {
  event: ProductService.Events.UPDATED,
  context: {
    subscriberId: "product-update-handler",
  },
}
Loaders

In this document, you’ll learn what loaders are in Medusa.
Overview

A loader is a script that runs when the Medusa backend starts. The Medusa backend uses loaders to initialize the database connection, load plugins, register resources in the dependency container, and more.

Loaders can be created within the Medusa backend codebase, in a plugin, or in a module to perform custom actions when the backend starts. The loader is created in a TypeScript or JavaScript file located in the src/loaders directory, then transpiled using the build command into the dist/loaders directory.
Loader Examples

For example, the Redis Event Bus module uses a loader to establish a connection with Redis and log a message in the console.

Another example is the Algolia plugin, which uses a loader to update the index settings when the Medusa backend starts based on the plugin’s options.

Loaders can be used to access the dependency container and register custom resources in it.

Loaders can also be used to create scheduled jobs that run at a specified interval of time.
How to Create a Loader

In this document, you’ll learn how to create a loader in Medusa. A loader can be created in the Medusa backend codebase, in a plugin, or in a module.
Step 1: Create Loader File

Create a TypeScript or JavaScript file in the src/loaders directory that will hold your custom script. There are no restrictions on the name of the file.

For example, create the file src/loaders/my-loader.ts that will hold the loader.
Step 2: Define the Loader

The loader file must export a function.
Parameters of Loaders in Medusa Backend and Plugins

When the loader is defined in the Medusa backend or a plugin, the function receives the following parameters:

    container: the first parameter, which is a MedusaContainer object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
    config: the second parameter, which is an object that holds the loader’s plugin options. If the loader is not created inside a plugin, the config object will be empty.

Parameters of Loaders in Modules

When the loader is defined in a module, it receives the following parameters:

    container: the first parameter, which is a MedusaContainer object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
    logger: the second parameter, which is a Logger object. The logger can be used to log messages in the console.
    config: the third parameter, which is an object that holds the loader’s module options.

Example Implementation

For example, this loader function resolves the ProductService and logs in the console the count of products in the Medusa backend:
src/loaders/my-loader.ts

import { 
  ProductService, 
  ConfigModule,
  MedusaContainer,
} from "@medusajs/medusa"

export default async (
  container: MedusaContainer,
  config: ConfigModule
): Promise<void> => {
  console.info("Starting loader...")
  const productService = container.resolve<ProductService>(
    "productService"
  )
  console.info(`Products count: ${
    await productService.count()
  }`)
  console.info("Ending loader...")
}
Notification Architecture Overview

This document gives an overview of the notification architecture and how it works.
Introduction

Medusa provides a Notification API to mainly handle sending and resending notifications when an event occurs. For example, sending an email to the customer when they place an order.

The Notification architecture is made up of two main components: the Notification Provider and the Notification. Simply put, the Notification Provider handles the sending and resending of a Notification.
Notification Provider

A Notification Provider is a provider that handles sending and resending of notifications. You can either create and integrate your own provider or install a Notification Provider through a third-party plugin.

An example of a notification provider is SendGrid. When an order is placed, the SendGrid plugin sends an email to the customer.
How Notification Provider is Created

A Notification Provider is essentially a Medusa Service with a unique identifier, and it extends the NotificationService provided by the medusa-interfaces package. It can be created as part of a Plugin, or it can be created just as a Service file in your Medusa backend.

As a developer, you mainly work with the Notification Provider when integrating a third-party service that handles notifications in Medusa.

When you run your Medusa backend, the Notification Provider is registered in your backend. If it's a new Notification Provider, it will be inserted into the notification_provider table in your database.
NotificationProvider Entity Overview

The NotificationProvider entity only has 2 attributes: id and is_installed.

id is the value of the static property identifier defined inside the notification Service class.

is_installed indicates whether the Notification Provider is installed or not. When you install a Notification Provider, the value of this attribute is true.

If you installed a Notification provider and then removed the Service files or plugin that registered the Notification Provider, the Notification Provider remains in your database, but the value of the is_installed field changes to false.
Notification

A notification is a form of an alert sent to the customers or users to inform them of an action that has occurred. For example, if an order is placed, the notification, in this case, can be an email that confirms their order and lists the order details.

Notifications can take on other forms such as an SMS or a Slack message.
How Notification is Created

Notifications are created in the NotificationService class in Medusa’s core after the Notification has been handled by the Notification Provider.

The data and additional details that the Notification Provider returns to the NotificationService is used to fill some of the attributes of the Notification in the database.

A Notification also represents a resent notification. So, when a notification is resent, a new one is created that references the original Notification as a parent. This Notification is also created by the NotificationService class.
Notification Entity Overview

The two most important properties in the Notification entity are the to and data properties.

The to property is a string that represents the receiver of the Notification. For example, if the Notification was sent to an email address, the to property holds the email address the Notification was sent to.

The to property can alternatively be a phone number or a chat username. It depends on the Notification Provider and how it sends the Notification.

The data property is an object that holds all the data necessary to send the Notification. For example, in the case of an order confirmation Notification, it can hold data related to the order.

The data property is useful when a notification is resent later. The same data can be used to resend the notification.

In the case of resent notifications, the resent notification has a parent_id set to the ID of the original Notification. The value of the parent_id property in the original Notification is null.

The Notification entity has some properties that determine the context of this Notification. This includes the event_name property which is the event that triggered the sending of this notification.

Additionally, the resource_type property is used to determine what resource this event is associated with. For example, if the event_name is order.placed, the resource_type is order.

You can also access the specific resource using the resource_id property, which is the ID of the resource. So, in case of the order.placed event, the resource_id is the ID of the order that was created.

The Notification entity also includes properties related to the receiver of the Notification. In case the receiver is a customer, the customer_id property is used to identify which customer.
Automating Flows with Notifications

With Medusa you can create notifications as a reaction to a wide spectrum of events, allowing you to automate communication and processes.

An example of a flow that can be implemented using Medusa's Notification API is automated return flows:

    A customer requests a return by sending a POST request to the /store/returns API Route.
    The Notification Provider listens to the order.return_requested event and sends an email to the customer with a return invoice and return label generated by the Fulfillment Provider.
    The customer returns the items triggering the return.received event.
    The Notification Provider listens to the return.received event and sends an email to the customer with confirmation that their items have been received and that a refund has been issued.


How to Create a Notification Provider

In this document, you’ll learn how to create a notification provider in the Medusa backend and the methods you must implement in it. Learn more about the notification architecture in this documentation
Overview
Prerequisites

Before creating a Notification Provider, install an event bus module.

A Notification Provider is a provider that handles sending and resending of notifications.

To create a Notification Provider, create a TypeScript or JavaScript file in src/services. The name of the file is the name of the provider (for example, sendgrid.ts). The file must export a class that extends the AbstractNotificationService class imported from @medusajs/medusa.

For example, create the file src/services/email-sender.ts with the following content:
src/services/email-sender.ts

import { AbstractNotificationService } from "@medusajs/medusa"
import { EntityManager } from "typeorm"

class EmailSenderService extends AbstractNotificationService {
  protected manager_: EntityManager
  protected transactionManager_: EntityManager

  sendNotification(
    event: string,
    data: unknown,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    throw new Error("Method not implemented.")
  }
  resendNotification(
    notification: unknown,
    config: unknown,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    throw new Error("Method not implemented.")
  }

}

export default EmailSenderService

Identifier Property

The NotificationProvider entity has 2 properties: identifier and is_installed. The value of the identifier property in the notification provider class is used when the Notification Provider is created in the database.

The value of this property is also used later when you want to subscribe the Notification Provider to events in a Loader.

For example:

class EmailSenderService extends AbstractNotificationService {
  static identifier = "email-sender"
  // ...
}

constructor

You can use the constructor of your notification provider to access the different services in Medusa through dependency injection.

You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.

Additionally, if you’re creating your notification provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
Example

// ...
import { AbstractNotificationService, OrderService } from "@medusajs/medusa"
import { EntityManager } from "typeorm"

class EmailSenderService extends AbstractNotificationService {
  // ...
  protected orderService: OrderService

  constructor(container, options) {
    super(container)
    // you can access options here in case you're
    // using a plugin

    this.orderService = container.orderService

    // you can also initialize a client that
    // communicates with a third-party service.
    this.client = new Client(options)
  }

  // ...
}

export default EmailSenderService

Parameters
containerRecord<string, unknown>Required
An instance of MedusaContainer that allows you to access other resources, such as services, in your Medusa backend.
configRecord<string, unknown>
If this notification provider is created in a plugin, the plugin's options are passed in this parameter.
Methods
sendNotification

When an event is triggered that your Notification Provider is registered as a handler for, the NotificationService in the Medusa backend executes this method of your Notification Provider.

In this method, you can perform the necessary operation to send the Notification. For example, you can send an email to the customer when they place an order.
Example

class EmailSenderService extends AbstractNotificationService {
  // ...
  async sendNotification(
    event: string,
    data: any,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    if (event === "order.placed") {
      // retrieve order
      const order = await this.orderService.retrieve(data.id)
      // TODO send email

      console.log("Notification sent")
      return {
        to: order.email,
        status: "done",
        data: {
          // any data necessary to send the email
          // for example:
          subject: "You placed a new order!",
          items: order.items,
        },
      }
    }
  }
  // ...
}

Parameters
eventstringRequired
The name of the event that was triggered. For example, order.placed.
dataunknownRequired
The data payload of the event that was triggered. For example, if the order.placed event is triggered, the eventData object contains the property id which is the ID of the order that was placed. You can refer to the Events reference for information on all events and their payloads.
attachmentGeneratorunknownRequired
If you’ve previously register an attachment generator to the NotificationService using the registerAttachmentGenerator method, you have access to it here. You can use the attachmentGenerator to generate on-demand invoices or other documents. The default value of this parameter is null.
Returns
PromisePromise<ReturnedData>Required
The sending details.
ReturnedDataReturnedDataRequired
The details of a sent or resent notification.
tostringRequired
The receiver of the Notification. For example, if you sent an email to the customer then to is the email address of the customer. In other cases, it might be a phone number or a username.
statusstringRequired
The status of the sent notification. There are no restriction on the returned status.
dataRecord<string, unknown>Required
The data used to send the Notification. For example, if you sent an order confirmation email to the customer, then the data object might include the order items or the subject of the email. This data is necessary if the notification is resent later as you can use the same data.
resendNotification

This method is used to resend notifications, which is typically triggered by the Resend Notification API Route.
Example

class EmailSenderService extends AbstractNotificationService {
  // ...
  async resendNotification(
    notification: any,
    config: any,
    attachmentGenerator: unknown
  ): Promise<{
      to: string;
      status: string;
      data: Record<string, unknown>;
    }> {
    // check if the receiver should be changed
    const to: string = config.to || notification.to

    // TODO resend the notification using the same data
    // that is saved under notification.data

    console.log("Notification resent")
    return {
      to,
      status: "done",
      data: notification.data, // make changes to the data
    }
  }
}

Parameters
notificationunknownRequired
The original Notification record that was created after you sent the notification with sendNotification. It includes the to and data attributes which are populated originally using the to and data properties of the object you return in sendNotification.
configunknownRequired
The new configuration used to resend the notification. The Resend Notification API Route, allows you to pass a new to field. If specified, it will be available in this config object.
attachmentGeneratorunknownRequired
f you’ve previously register an attachment generator to the NotificationService using the registerAttachmentGenerator method, you have access to it here. You can use the attachmentGenerator to generate on-demand invoices or other documents. The default value of this parameter is null.
Returns
PromisePromise<ReturnedData>Required
The resend details.
ReturnedDataReturnedDataRequired
The details of a sent or resent notification.
tostringRequired
The receiver of the Notification. For example, if you sent an email to the customer then to is the email address of the customer. In other cases, it might be a phone number or a username.
statusstringRequired
The status of the sent notification. There are no restriction on the returned status.
dataRecord<string, unknown>Required
The data used to send the Notification. For example, if you sent an order confirmation email to the customer, then the data object might include the order items or the subject of the email. This data is necessary if the notification is resent later as you can use the same data.
Subscribe with Loaders

After creating your Notification Provider Service, you must create a Loader that registers this Service as a notification handler of events.

For example, to register the email-sender Notification Provider as a handler for the order.placed event, create the file src/loaders/notification.ts with the following content:
src/loaders/notification.ts

import { 
  MedusaContainer, 
  NotificationService,
} from "@medusajs/medusa"

export default async (
  container: MedusaContainer
): Promise<void> => {
  const notificationService = container.resolve<
    NotificationService
  >("notificationService")

  notificationService.subscribe(
    "order.placed", 
    "email-sender"
  )
}

This loader accesses the notificationService through the MedusaContainer. The notificationService has a subscribe method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.
Test Sending a Notification

Make sure you have an event bus module configured in your Medusa backend. You can learn more on how to do that in the Configurations guide.

Then:

1. Run the build command in the root directory of your Medusa backend.

2. Start your Medusa backend.
3. Place an order either using the REST APIs or using the storefront.

4. After placing an order, you can see in your console the message “Notification Sent”. If you added your own notification sending logic, you should receive an email or alternatively the type of notification you’ve set up.
Test Resending a Notification

To test resending a notification:

    Retrieve the ID of the notification you just sent using the List Notifications API Route. You can pass as a body parameter the to or event_name parameters to filter out the notification you just sent.

    Send a request to the Resend Notification API Route using the ID retrieved from the previous request. You can pass the to parameter in the body to change the receiver of the notification.

    You should see the message “Notification Resent” in your console.

AdminResetPasswordRequest

The details of the password reset request.
Properties
tokenstringRequired
The password-reset token generated when the password reset was requested.
passwordstringRequired
The User's new password.
emailstring
The User's email.

Medusa Backend Directory Structure

In this document, you’ll learn about the directory structure of a Medusa backend. It’ll help you understand the purpose of each file and folder in your Medusa backend project.
Root Files

These are files present at the root of your Medusa backend.
.babelrc.js

Defines Babel’s configurations, which are used when running the build command that transpiles files from the src directory to the dist directory.
.env

Includes the values of environment variables. This is typically only used in development. In production you should define environment variables based on your hosting provider.
.env.template

Gives an example of what variables may be included in .env.
.gitignore

Specifies files that shouldn’t be committed to a Git repository.
.yarnrc.yml

Ensures dependencies are always installed in node-modules. This ensures compatibility with pnpm.
index.js

Defines an entry file, which is useful when starting the Medusa backend with a process manager like pm2.
medusa-config.js

Defines the Medusa backend’s configurations, including the database configurations, plugins used, modules used, and more.

Read more: Medusa backend configurations.
package.json

Since the Medusa backend is an NPM package, this file defines its information as well as its dependencies. It will also include any new dependencies you install.
README.md

Provides general information about the Medusa backend.
tsconfig.admin.json

Defines the TypeScript configurations that are used to transpile admin customization files. So, it only works for files under the src/admin directory.
tsconfig.json

Defines the general TypeScript configurations used to transpile files from the src directory to the dist directory.
tsconfig.server.json

Defines the TypeScript configurations that are used to transpile Medusa backend customization files. It works for all files except for the files under the src/admin directory.
tsconfig.spec.json

Defines TypeScript configurations for test files. These are files that either reside under a __tests__ directory under src, or that have a file name ending with one of the following:

    .test.ts or .test.js
    .spec.ts or .test.js

yarn.lock or package-lock.json

An automatically generated file by yarn or npm that holds the current versions of all dependencies installed to ensure the correct versions are always installed.

If you used the create-medusa-app command to install the Medusa backend, it’ll attempt to use yarn by default to install the dependencies. If yarn is not installed on your machine, it will then fall back to using npm.

Based on the package manager used to install the dependencies, either yarn.lock or package-lock.json will be available, or both.
Root Directories

These are the directories present at the root of your Medusa backend.
.cache

This directory will only be available if you have the Medusa Admin installed and you’ve already started your Medusa backend at least once before. It holds all cached files related to building the Medusa Admin assets.
build

This directory will only be available if you have the Medusa Admin installed and you’ve either built your admin files or ran the Medusa backend at least once before. It holds the built files that are used to serve the admin in your browser.
data

This directory holds a JSON file used to seed your Medusa backend with dummy data which can be useful for demo purposes. The data is seeded automatically if you include the --seed option when using either the create-medusa-app or medusa new commands.

You can also seed the data by running the following command:

    npm
    Yarn
    pnpm

yarn seed

dist

This directory holds the transpiled Medusa backend customizations. This directory may not be available when you first install the Medusa backend. It’ll be available when you run the build command or start your Medusa backend with the dev command.

The files under this directory are the files that are used in your Medusa backend. So, when you make any changes under src, make sure the changes are transpiled into the dist directory. If you’re using the dev or medusa develop commands, this is handled automatically whenever changes occur under the src directory.
node_modules

This directory holds all installed dependencies in your project.
src

This directory holds all Medusa backend and admin customizations. More details about each subdirectory are included in this section.
uploads

This directory holds all file uploads to the Medusa backend. It’s only used if you’re using the Local File Service plugin, which is installed by default.
src Subdirectories

Files under the src directory hold the Medusa backend and admin customizations. These files should later be transpiled into the dist directory for them to be used during the backend’s runtime.

If any of these directories are not available, you can create them yourself.
admin

This directory holds all Medusa Admin customizations. The main subdirectories of this directory are:

    widgets: Holds all Medusa Admin widgets.
    routes: Holds all Medusa Admin UI routes.

api

This directory holds all custom API Routes, which are defined in route.ts or route.js files. These files can be created in sub-directories of the api directory based on the API Route's path.

Read more: API Routes
loaders

This directory holds scripts that run when the Medusa backend starts. For example, the scripts can define a scheduled job.

Read more: Loaders
migrations

This directory holds all migration scripts that reflect changes on the database the Medusa backend is connected to.

Read more: Migrations
models

This directory holds all custom entities, which represent tables in your database. You can create a new entity, or customize a Medusa entity.

Read more: Entities
repositories

This directory holds all custom repositories which provide utility methods to access and modify data related to an entity.

Read more: Repositories
services

This directory holds all custom services. Services define utility methods related to an entity or feature that can be used across the Medusa backend’s resources.

Read more: Services
subscribers

This directory holds all custom subscribers. Subscribers listen to emitted events and registers method to handle them.

Read more: Subscribers


Configure Medusa Backend

In this document, you’ll learn how to create a file service in the Medusa backend and the methods you must implement in it.
Prerequisites

This document assumes you already followed along with the Prepare Environment documentation and have a Medusa backend installed.

The configurations for your Medusa backend are in medusa-config.js located in the root of your Medusa project. The configurations include database, modules, and plugin configurations, among other configurations.

medusa-config.js exports an object having the following properties:

    projectConfig: (required): An object that holds general configurations related to the Medusa backend, such as database or CORS configurations.
    plugins: An array of plugin configurations that defines what plugins are installed and optionally specifies each of their configurations.
    modules: An object that defines what modules are installed and optionally specifies each of their configurations.
    featureFlags: An object that enables or disables features guarded by a feature flag.

For example:
medusa-config.js

module.exports = {
  projectConfig,
  plugins,
  modules,
  featureFlags,
}

Environment Variables

It's highly recommended to store the values of configurations in environment variables, then reference them within medusa-config.js.

During development, you can set your environment variables in the .env file at the root of your Medusa backend project. In production, setting the environment variables depends on the hosting provider.
projectConfig

This property holds essential configurations related to the Medusa backend, such as database and CORS configurations.
store_cors

The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.

store_cors is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.

Every origin in that list must either be:

    A URL. For example, http://localhost:8000. The URL must not end with a backslash;
    Or a regular expression pattern that can match more than one origin. For example, .example.com. The regex pattern that the backend tests for is ^([/~@;%#'])(.*?)\1([gimsuy]*)$.

Example

Some example values of common use cases:

# Allow different ports locally starting with 800
STORE_CORS=/http://localhost:800\d+$/

# Allow any origin ending with vercel.app. For example, storefront.vercel.app
STORE_CORS=/vercel\.app$/

# Allow all HTTP requests
STORE_CORS=/http://.+/

Then, set the configuration in medusa-config.js:
medusa-config.js

module.exports = {
  projectConfig: {
    store_cors: process.env.STORE_CORS,
    // ...
  },
  // ...
}

If you’re adding the value directly within medusa-config.js, make sure to add an extra escaping / for every backslash in the pattern. For example:
medusa-config.js

module.exports = {
  projectConfig: {
    store_cors: "/vercel\\.app$/",
    // ...
  },
  // ...
}

admin_cors

The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.

admin_cors is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.

Every origin in that list must either be:

    A URL. For example, http://localhost:7001. The URL must not end with a backslash;
    Or a regular expression pattern that can match more than one origin. For example, .example.com. The regex pattern that the backend tests for is ^([/~@;%#'])(.*?)\1([gimsuy]*)$.

Example

Some example values of common use cases:

# Allow different ports locally starting with 700
ADMIN_CORS=/http://localhost:700\d+$/

# Allow any origin ending with vercel.app. For example, admin.vercel.app
ADMIN_CORS=/vercel\.app$/

# Allow all HTTP requests
ADMIN_CORS=/http://.+/

Then, set the configuration in medusa-config.js:
medusa-config.js

module.exports = {
  projectConfig: {
    admin_cors: process.env.ADMIN_CORS,
    // ...
  },
  // ...
}

If you’re adding the value directly within medusa-config.js, make sure to add an extra escaping / for every backslash in the pattern. For example:
medusa-config.js

module.exports = {
  projectConfig: {
    admin_cors: "/http:\\/\\/localhost:700\\d+$/",
    // ...
  },
  // ...
}

cookie_secret

A random string used to create cookie tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.

In a development environment, if this option is not set, the default secret is supersecret However, in production, if this configuration is not set, an error is thrown and the backend crashes.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    cookie_secret: process.env.COOKIE_SECRET ||
      "supersecret",
    // ...
  },
  // ...
}

jwt_secret

A random string used to create authentication tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.

In a development environment, if this option is not set the default secret is supersecret However, in production, if this configuration is not set an error, an error is thrown and the backend crashes.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    jwt_secret: process.env.JWT_SECRET ||
      "supersecret",
    // ...
  },
  // ...
}

database_database

The name of the database to connect to. If specified in database_url, then it’s not required to include it.

Make sure to create the PostgreSQL database before using it. You can check how to create a database in PostgreSQL's documentation.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    database_database: process.env.DATABASE_DATABASE || 
      "medusa-store",
    // ...
  },
  // ...
}

database_url

The connection URL of the database. The format of the connection URL for PostgreSQL is:

postgres://[user][:password]@[host][:port]/[dbname]

Where:

    [user]: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the postgres superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in PostgreSQL's documentation.
    [:password]: an optional password for the user. When provided, make sure to put : before the password.
    [host]: (required) your PostgreSQL host. When run locally, it should be localhost.
    [:post]: an optional port that the PostgreSQL server is listening on. By default, it's 5432. When provided, make sure to put : before the port.
    [dbname]: (required) the name of the database.

You can learn more about the connection URL format in PostgreSQL’s documentation.
Example

For example, set the following database URL in your environment variables:

DATABASE_URL=postgres://postgres@localhost/medusa-store

Then, use the value in medusa-config.js:
medusa-config.js

module.exports = {
  projectConfig: {
    database_url: process.env.DATABASE_URL,
    // ...
  },
  // ...
}

database_schema

The database schema to connect to. This is not required to provide if you’re using the default schema, which is public.
medusa-config.js

module.exports = {
  projectConfig: {
    database_schema: process.env.DATABASE_SCHEMA || 
      "custom",
    // ...
  },
  // ...
}

database_logging

This configuration specifies what database messages to log. Its value can be one of the following:

    (default) A boolean value that indicates whether any messages should be logged.
    The string value all that indicates all types of messages should be logged.
    An array of log-level strings to indicate which type of messages to show in the logs. The strings can be query, schema, error, warn, info, log, or migration. Refer to Typeorm’s documentation for more details on what each of these values means.

If this configuration isn't set, its default value is false, meaning no database messages are logged.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    database_logging: [
      "query", "error",
    ],
    // ...
  },
  // ...
}

database_extra

An object that includes additional configurations to pass to the database connection. You can pass any configuration. One defined configuration to pass is ssl which enables support for TLS/SSL connections.

This is useful for production databases, which can be supported by setting the rejectUnauthorized attribute of ssl object to false. During development, it’s recommended not to pass this option.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    database_extra: 
      process.env.NODE_ENV !== "development"
        ? { ssl: { rejectUnauthorized: false } }
        : {},
    // ...
  },
  // ...
}

Properties
sslobjectRequired
Configure support for TLS/SSL connection
redis_url

Used to specify the URL to connect to Redis. This is only used for scheduled jobs. If you omit this configuration, scheduled jobs won't work.
Note

You must first have Redis installed. You can refer to Redis's installation guide.

The Redis connection URL has the following format:

redis[s]://[[username][:password]@][host][:port][/db-number]

For a local Redis installation, the connection URL should be redis://localhost:6379 unless you’ve made any changes to the Redis configuration during installation.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    redis_url: process.env.REDIS_URL || 
      "redis://localhost:6379",
    // ...
  },
  // ...
}

redis_prefix

The prefix set on all keys stored in Redis. The default value is sess:.

If this configuration option is provided, it is prepended to sess:.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    redis_prefix: process.env.REDIS_PREFIX || 
      "medusa:",
    // ...
  },
  // ...
}

redis_options

An object of options to pass ioredis. You can refer to ioredis’s RedisOptions documentation for the list of available options.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    redis_options: {
      connectionName: process.env.REDIS_CONNECTION_NAME || 
        "medusa",
    },
    // ...
  },
  // ...
}

session_options

An object of options to pass to express-session.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    session_options: {
      name: process.env.SESSION_NAME || 
        "custom",
    },
    // ...
  },
  // ...
}

Properties
namestring
The name of the session ID cookie to set in the response (and read from in the request). The default value is connect.sid. Refer to express-session’s documentation for more details.
resaveboolean
Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is true. Refer to express-session’s documentation for more details.
rollingboolean
Whether the session identifier cookie should be force-set on every response. The default value is false. Refer to express-session’s documentation for more details.
saveUninitializedboolean
Whether a session that is "uninitialized" is forced to be saved to the store. The default value is true. Refer to express-session’s documentation for more details.
secretstring
The secret to sign the session ID cookie. By default, the value of cookie_secret is used. Refer to express-session’s documentation for details.
ttlnumber
Used when calculating the Expires Set-Cookie attribute of cookies. By default, its value is 10 * 60 * 60 * 1000. Refer to express-session’s documentation for details.
http_compression

Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.

Its value is an object that has the following properties:

If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header "x-no-compression": true.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    http_compression: {
      enabled: true,
      level: 6,
      memLevel: 8,
      threshold: 1024,
    },
    // ...
  },
  // ...
}

Properties
enabledboolean
Whether HTTP compression is enabled. By default, it's false.
levelnumber
The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete. A lower level will result in less compression but will be much faster. The default value is 6.
memLevelnumber
How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level). The default value is 8.
thresholdstring | number
The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the bytes module. The default value is 1024.
jobs_batch_size

Configure the number of staged jobs that are polled from the database. Default is 1000.
Example
medusa-config.js

module.exports = {
  projectConfig: {
    jobs_batch_size: 100
    // ...
  },
  // ...
}

plugins

On your Medusa backend, you can use Plugins to add custom features or integrate third-party services. For example, installing a plugin to use Stripe as a payment processor.

Aside from installing the plugin with NPM, you need to pass the plugin you installed into the plugins array defined in medusa-config.js.

The items in the array can either be:

    A string, which is the name of the plugin to add. You can pass a plugin as a string if it doesn’t require any configurations.
    An object having the following properties:
        resolve: The name of the plugin.
        options: An object that includes the plugin’s options. These options vary for each plugin, and you should refer to the plugin’s documentation for available options.

Example
medusa-config.js

module.exports = {
  plugins: [
    `medusa-my-plugin-1`,
    {
      resolve: `medusa-my-plugin`,
      options: {
        apiKey: process.env.MY_API_KEY || 
          `test`,
      },
    },
    // ...
  ],
  // ...
}

modules

In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain modules with custom implementations.

Aside from installing the module with NPM, you must add it to the exported object in medusa-config.js.

The keys of the modules configuration object refer to the type of module. Its value can be one of the following:

    A boolean value indicating whether the module type is enabled;
    Or a string value indicating the name of the module to be used for the module type. This can be used if the module does not require any options;
    Or an object having the following properties, but typically you would mainly use the resolve and options properties only:
        resolve: a string indicating the name of the module.
        options: an object indicating the options to pass to the module. These options vary for each module, and you should refer to the module’s documentation for details on them.
        resources: a string indicating whether the module shares the dependency container with the Medusa core. Its value can either be shared or isolated. Refer to the Modules documentation for more details.
        alias: a string indicating a unique alias to register the module under. Other modules can’t use the same alias.
        main: a boolean value indicating whether this module is the main registered module. This is useful when an alias is used.

Example
medusa-config.js

module.exports = {
  modules: {
    eventBus: {
      resolve: "@medusajs/event-bus-local",
    },
    cacheService: {
      resolve: "@medusajs/cache-redis",
      options: { 
        redisUrl: process.env.CACHE_REDIS_URL,
        ttl: 30,
      },
    },
    // ...
  },
  // ...
}

featureFlags

Some features in the Medusa backend are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability.

You can specify whether a feature should or shouldn’t be used in your backend by enabling its feature flag. Feature flags can be enabled through either environment variables or through this configuration exported in medusa-config.js.

If you want to use the environment variables method, learn more about it in the Feature Flags documentation.

The featureFlags configuration is an object. Its properties are the names of the feature flags. Each property’s value is a boolean indicating whether the feature flag is enabled.

You can find available feature flags and their key name here.
Example
medusa-config.js

module.exports = {
  featureFlags: {
    product_categories: true,
    // ...
  },
  // ...
}

Note

After enabling a feature flag, make sure to run migrations as it may require making changes to the database.

Medusa Architecture Overview

In this document, you'll get an overview of Medusa's architecture to better understand how all resources and tools work together.
Architecture Overview

Medusa's core package @medusajs/medusa is a Node.js headless server. It combines all the Commerce Modules that Medusa provides. Commerce Modules are ecommerce features that can be used as building blocks in an ecommerce ecosystem. Product is an example of a Commerce Module.

Medusa Core Architecture

The backend connects to a database, such as PostgreSQL, to store the ecommerce store’s data. The tables in that database are represented by Entities, built on top of Typeorm. Entities can also be reflected in the database using Migrations.

The retrieval, manipulation, and other utility methods related to that entity are created inside a Service. Services are TypeScript or JavaScript classes that, along with other resources, can be accessed throughout the Medusa backend through dependency injection.

The backend doesn't have any tightly-coupled frontend. Instead, it exposes API Routes which are REST APIs that frontends such as an admin or a storefront can use to communicate with the backend.

Medusa also uses an Events Architecture to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as Redis.

Events can be handled using Subscribers. Subscribers are TypeScript or JavaScript files that export a handler function that's executed asynchronously when an event is triggered, and a configuration object defining what events the subscriber listens to.

You can create any of the resources in the backend’s architecture, such as entities, API Routes, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses loaders to load the backend’s resources, as well as your custom resources and resources in Plugins.

You can package your customizations into Plugins to reuse them in different Medusa backends or publish them for others to use. You can also install existing plugins into your Medusa backend.

Dependency Container and Injection

In this document, you’ll learn what the dependency container is and how you can use it in Medusa with dependency injection.
Introduction
What is Dependency Injection

Dependency Injection is the act of delivering the required resources to a class. These resources are the class’s dependencies. This is usually done by passing (or injecting) the dependencies in the constructor of the class.

Generally, all resources are registered in a container. Then, whenever a class depends on one of these resources, the system retrieves the resources from the container and injects them into the class’s constructor.
Medusa’s Dependency Container

Medusa uses a dependency container to register essential resources of the backend. You can then access these resources in classes and API Routes using the dependency container.

For example, if you create a custom service, you can access any other service registered in Medusa in your service’s constructor. That includes Medusa’s core services, services defined in plugins, or other services that you create on your backend.

You can load more than services in your Medusa backend. You can load the Entity Manager, logger instance, and much more.
MedusaContainer

To manage dependency injections, Medusa uses Awilix. Awilix is an NPM package that implements dependency injection in Node.js projects.

When you run the Medusa backend, a container of the type MedusaContainer is created. This type extends the AwilixContainer object.

The backend then registers all important resources in the container, which makes them accessible in classes and API Routes.
Registered Resources

The Medusa backend scans the core Medusa package, plugins, and your files in the dist directory and registers the following resources:
Tip

The Lifetime column indicates the lifetime of a service. Other resources that aren't services don't have a lifetime, which is indicated with the - in the column. You can learn about what a lifetime is in the Create a Service documentation.

Resource
	

Description
	

Registration Name
	

Lifetime

Configurations
	

The configurations that are exported from medusa-config.js.
	

configModule
	

-

Services
	

Services that extend the TransactionBaseService class.
	

Each service is registered under its camel-case name. For example, the ProductService is registered as productService.
	

Core services by default have the SINGLETON lifetime. However, some have a different lifetime which is indicated in this table. Custom services, including services in plugins, by default have the SCOPED lifetime, unless defined differently within the custom service.

Entity Manager
	

An instance of Typeorm’s Entity Manager.
	

manager
	

-

Logger
	

An instance of Medusa CLI’s logger. You can use it to log messages to the terminal.
	

logger
	

-

Single Payment Processor
	

An instance of every payment processor that extends the AbstractPaymentService or the AbstractPaymentProcessor classes.
	

Every payment processor is registered under two names:

    Its camel-case name of the processor. For example, the StripeProviderService is registered as stripeProviderService.
    pp_ followed by its identifier. For example, the StripeProviderService is registered as pp_stripe.

	

By default, it's SINGLETON unless defined differently within the payment processor service.

All Payment Processors
	

An array of all payment processor that extend the AbstractPaymentService or AbstractPaymentProcessor class.
	

paymentProviders
	

paymentProviders is TRANSIENT, and each item in it is SINGLETON.

Single Fulfillment Provider
	

An instance of every fulfillment provider that extends the FulfillmentService class.
	

Every fulfillment provider is registered under two names:

    Its camel-case name. For example, the WebshipperFulfillmentService is registered as webshipperFulfillmentService.
    fp_ followed by its identifier. For example, the WebshipperFulfillmentService is registered as fp_webshipper.

	

By default, it's SINGLETON unless defined differently within the fulfillment provider service.

All Fulfillment Providers
	

An array of all fulfillment providers that extend the FulfillmentService class.
	

fulfillmentProviders
	

fulfillmentProviders is TRANSIENT, and each item in it is SINGLETON.

Single Notification Provider
	

An instance of every notification provider that extends the AbstractNotificationService or the BaseNotificationService classes.
	

Every notification provider is registered under two names:

    Its camel-case name. For example, the SendGridService is registered as sendGridService.
    noti_ followed by its identifier. For example, the SendGridService is registered as noti_sendgrid.

	

By default, it's SINGLETON unless defined differently within the notification provider service.

All Notification Providers
	

An array of all notification providers that extend the AbstractNotificationService or the BaseNotificationService classes.
	

notificationProviders
	

notificationProviders is TRANSIENT, and each item in it is SINGLETON.

File Service
	

An instance of the class that extends the FileService class, if any.
	

The file service is registered under two names:

    Its camel-case name. For example, the MinioService is registered as minioService.
    fileService

	

By default, it's SINGLETON unless defined differently within the file service.

Search Service
	

An instance of the class that extends the AbstractSearchService or the SearchService classes, if any.
	

The search service is registered under two names:

    Its camel-case name. For example, the AlgoliaService is registered as algoliaService.
    searchService

	

By default, it's SINGLETON unless defined differently within the search service.

Single Tax Provider
	

An instance of every tax provider that extends the AbstractTaxService class.
	

The tax provider is registered under two names:

    Its camel-case name.
    tp_ followed by its identifier.

	

By default, it's SINGLETON unless defined differently within the tax provider service.

All Tax Providers
	

An array of every tax provider that extends the AbstractTaxService class.
	

taxProviders
	

taxProviders is TRANSIENT, and each item in it is SINGLETON.

Oauth Services
	

An instance of every service that extends the OauthService class.
	

Each Oauth Service is registered under its camel-case name followed by Oauth.
	

By default, it's SINGLETON unless defined differently within the Oauth service.

Feature Flag Router
	

An instance of the FlagRouter. This can be used to list feature flags, set a feature flag’s value, or check if they’re enabled.
	

featureFlagRouter
	

-

Redis
	

An instance of the Redis client. If Redis is not configured, a fake Redis client is registered.
	

redisClient
	

-

Single Entity
	

An instance of every entity.
	

Each entity is registered under its camel-case name followed by Model. For example, the CustomerGroup entity is stored under customerGroupModel.
	

-

All Entities
	

An array of all database entities that is passed to Typeorm when connecting to the database.
	

db_entities
	

-

Repositories
	

An instance of each repository.
	

Each repository is registered under its camel-case name. For example, CustomerGroupRepository is stored under customerGroupRepository.
	

-

Single Batch Job Strategy
	

An instance of every class extending the AbstractBatchJobStrategy class.
	

Each batch job strategy is registered under three names:

    Its camel-case name. For example, ProductImportStrategy is registered as productImportStrategy.
    batch_ followed by its identifier. For example, the ProductImportStrategy is registered under batch_product-import-strategy.
    batchType_ followed by its batch job type. For example, the ProductImportStrategy is registered under batchType_product-import.

	

-

All Batch Job Strategies
	

An array of all classes extending the AbstractBatchJobStrategy abstract class.
	

batchJobStrategies
	

-

Tax Calculation Strategy
	

An instance of the class implementing the ITaxCalculationStrategy interface.
	

taxCalculationStrategy
	

-

Cart Completion Strategy
	

An instance of the class extending the AbstractCartCompletionStrategy class.
	

cartCompletionStrategy
	

-

Price Selection Strategy
	

An instance of the class implementing the IPriceSelectionStrategy interface.
	

priceSelectionStrategy
	

-

Strategies
	

An instance of strategies that aren’t of the specific types mentioned above and that are under the strategies directory.
	

Its camel-case name.
	

-
Resolve Resources

This section covers how to resolve resources from the dependency container to use them in API Routes and classes in general.
In API Routes

To resolve resources, such as services, in API Routes, use the MedusaRequest object's scope.resolve method. The method receives the registration name of the resource as a parameter.

For example:

const productService: ProductService = req.scope.resolve(
  "productService"
)

Please note that in API Routes some resources, such as repositories, aren't available. Refer to the repositories documentation to learn how you can load them.
In Subscribers

To resolve resources, such as services, in subscriber handler functions, use the container property of the handler function's parameter. The container has a method resolve which accepts the registration name of a resource as a parameter.

For example:

import { 
  ProductService,
  type SubscriberConfig, 
  type SubscriberArgs,
} from "@medusajs/medusa"

export default async function productUpdateHandler({ 
  data, eventName, container, pluginOptions, 
}: SubscriberArgs<Record<string, string>>) {
  const productService: ProductService = container.resolve(
    "productService"
  )

  // ...
}

// ...

In Classes

In classes such as services or strategies, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.

For example:

import { OrderService } from "@medusajs/medusa"

class OrderSubscriber {
  protected orderService: OrderService

  constructor({ orderService }) {
    this.orderService = orderService
  }
}
Modules

In this document, you’ll learn what Modules are and how can you use them during your custom development with Medusa.
Overview

Modules are self-contained, reusable pieces of code that encapsulate specific functionality or features within an ecommerce application. They foster separation of concerns, maintainability, and reusability by organizing code into smaller, independent units that can be easily managed, tested, and integrated with other modules.

Modules further increase Medusa’s extensibility. commerce modules, such as the cart engine, can be extended or entirely replaced with your own custom logic. They can also run independently of the core Medusa package, allowing you to utilize the commerce module within a larger commerce ecosystem. For example, you can use the Order Module as an Order Management System (OMS) without using Medusa’s core.

This also applies to core logic such as caching or events systems. You can use modules to integrate any logic or third-party service to handle this logic. This gives you greater flexibility in how you choose your tech stack.

Modules are created and loaded similarly to plugins. They can be loaded from a local project, or they can be installed and loaded from an NPM package. In the Medusa backend, they’re added as part of the configurations in medusa-config.js to use and load them within the backend.

How to Create a Module

In this document, you’ll learn how to create a module and test the module in your Medusa backend.

This document covers the general steps of creating an internal module, but does not explore actually implementing any functionality in it. An internal module is a TypeScript/JavaScript module that is loaded by the Medusa backend as part of the commerce application to extend or replace a functionality within it, such as the cache or event bus functionality.
(Optional) Step 0: Project Preparation

Before you start implementing the custom functionality in your module, it's recommended to create a directory that holds your module and prepare the following structure in it:

custom-module
|
|___ src
    |
    |___ index.ts
    |
    |___ services // directory

The directory can be an NPM project, but that is optional. index.ts acts as an entry point to your Module. You'll learn about its content in a later step. The service directory will hold your custom services. If you're adding other resources you can add other directories for them. For example, if you're adding an entity you can add a models directory.
Tip

You can use JavaScript instead of TypeScript.

It's also recommended to use the following TypeScript config in tsconfig.json, which should be added in the root of the project holding your module:

{
  "compilerOptions": {
    "lib": [
      "es2020"
    ],
    "target": "ES2020",
    "outDir": "./dist",
    "esModuleInterop": true,
    "declaration": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "sourceMap": true,
    "noImplicitReturns": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "allowJs": true,
    "skipLibCheck": true,
  },
  "include": ["src"],
  "exclude": [
    "dist",
    "./src/**/__tests__",
    "./src/**/__mocks__",
    "./src/**/__fixtures__",
    "node_modules"
  ]
}

Step 1: Implement the Custom Functionality

This step depends on what you’re actually implementing. For example, you can implement the cache or events module, or you can implement a commerce module. If what you’re creating has a guide, you can refer to it while implementing the functionalities.
Note About Project Structure

When developing your module, it's important to note that you'll later be referencing the module using a file path to an index.ts or index.js file. This file is explained in the next step and acts as an entry point and a definition of your module.

So, make sure when implementing your module you take into account that the module should be easily referenced from your local Medusa server. For example, you can develop your module in a sibling directory of the Medusa backend that you can reference with ../custom-module.

Keep in mind that when you publish the module to NPM, you'll have to move your module into a new NPM project. This is covered in the Publish Module documentation.
Recommended Guides
Create a Cache Module

Learn how to create a cache module in Medusa.
Create an Events Module

Learn how to create an events module in Medusa.
Step 2: Export Module

After implementing the module, you must export a module object that helps the Medusa backend understand how to use this Module. This is done in the file index.ts.

The file must export an object with the following properties:

type ModuleExports = {
  service: Constructor<any>
  loaders?: ModuleLoaderFunction[]
  migrations?: any[]
  models?: Constructor<any>[]
  runMigrations?(
    options: LoaderOptions,
    moduleDeclaration: InternalModuleDeclaration
  ): Promise<void>
  revertMigration?(
    options: LoaderOptions,
    moduleDeclaration: InternalModuleDeclaration
  ): Promise<void>
}

Tip

All property types such as ModuleLoaderFunction can be loaded from the @medusajs/modules-sdk package.

Where:

    service: This is the only required property to be exported. It should be the main service your module exposes, and it must implement all the declared methods on the module interface. For example, if it's a cache module, it must implement the ICacheService interface exported from @medusajs/types.
    loaders: (optional) an array of loader functions used to perform an action while loading the module. For example, you can log a message that the module has been loaded, or if your module's scope is isolated you can use the loader to establish a database connection.
    migrations: (optional) an array of objects containing database migrations that should run when the migration command is used with Medusa's CLI.
    models: (optional) an array of entities that your module creates.
    runMigrations: (optional) a function that can be used to define migrations to run when the migration run command is used with Medusa's CLI. The migrations will only run if they haven't already. This will only be executed if the module's scope is isolated.
    revertMigration: (optional) a function can be used to define how migrations should be reverted when the migration revert command is used with Medusa's CLI. This will only be executed if the module's scope is isolated.

Here's an example implementation of index.ts from Medusa's Redis Cache module:
index.ts

import { ModuleExports } from "@medusajs/modules-sdk"

import Loader from "./loaders"
import { RedisCacheService } from "./services"

const service = RedisCacheService
const loaders = [Loader]

const moduleDefinition: ModuleExports = {
  service,
  loaders,
}

export default moduleDefinition

Step 3: Reference Module

To use your module in the Medusa backend, add your module to medusa-config.js:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    moduleType: {
      resolve: "module-name-or-path", 
      options: {
        // options if necessary
      },
      // optional
      resources: "shared",
    },
  },
}

The way you add your module depends on its type and what options it requires, if any. Note that in the above code example:

    moduleType is the type of your module. For example, if your module is a cache module, it should be changed to cacheService.
    resolve is used to reference the Module. Its value should be either the name of an NPM package module, or a relative file path to the module. This is explained more in the Module Reference section.
    options should hold any options of your module, if necessary.
    resources is an optional property that indicates whether the module shares the same dependency container as the rest of the resources in the Medusa backend. More details are explained in the Module Scope section.

Module Reference

When the module is installed as an NPM package, the value of the resolve property should be the name of that package. For example:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    moduleType: {
      resolve: "custom-module", 
      // ...
    },
  },
}

However, when using a local module, you must reference the module using a relative file path to it from the Medusa backend.

For example, consider you have the following file structure:

|
|___ custom-module
|    |
|    |___ src
|        |
|        |___ index.ts
|        |
|        |___ services
|        |    |
|        |    |___ custom-service.ts
|        |___ // more files
|
|
|___ medusa-backend

You can reference your module in two ways:

1. Referencing the directory: In this case, it's assumed that the index.ts file that contains the module definition is in the root of the directory you referenced. Using the above example, the file path would be in this case:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    moduleType: {
      resolve: "../custom-module/src", 
      // ...
    },
  },
}

2. Referencing index file: In this case, it's assumed that the index.ts or index.js file you're referencing includes the module definition. Using the above example, the file path would be in this case:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    moduleType: {
      resolve: "../custom-module/src/index.ts", 
      // ...
    },
  },
}

Module Scope

By default, the module shares the same dependency container used across the Medusa backend. So, the module can benefit from the core services and other resources available through dependency injection. The module can also benefit from the same database connection.

The module's scope can be changed using the resources property available as part of the module's configurations:
medusa-config.js

module.exports = {
  // ...
  modules: { 
    // ...
    moduleType: {
      // other configurations
      resources: "shared",
    },
  },
}

The resources property can have one of the following values:

    shared: (default) The dependency container is shared with the module, including the database connection. You don't need to establish the database connection yourself in a loader.
    isolated: the module receives an empty dependency container, and only its own dependencies will be registered in the container. When using this value, you must establish the database connection yourself and managing other resources within your module.


Loaders

In this document, you’ll learn what loaders are in Medusa.
Overview

A loader is a script that runs when the Medusa backend starts. The Medusa backend uses loaders to initialize the database connection, load plugins, register resources in the dependency container, and more.

Loaders can be created within the Medusa backend codebase, in a plugin, or in a module to perform custom actions when the backend starts. The loader is created in a TypeScript or JavaScript file located in the src/loaders directory, then transpiled using the build command into the dist/loaders directory.
Loader Examples

For example, the Redis Event Bus module uses a loader to establish a connection with Redis and log a message in the console.

Another example is the Algolia plugin, which uses a loader to update the index settings when the Medusa backend starts based on the plugin’s options.

Loaders can be used to access the dependency container and register custom resources in it.

Loaders can also be used to create scheduled jobs that run at a specified interval of time.

Workflows Introduction

A workflow is a series of queries and actions that complete a task. Workflows are made up of a series of steps that interact with Medusa’s commerce modules, custom services, or external systems.

You construct a Workflow similar to how you create a JavaScript function, but unlike regular functions, a Medusa Workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and, if necessary, roll back steps.

Workflows can be used to define a flow with interactions across multiple systems, integrate third-party services into your commerce application, or automate actions within your application. Any flow with a series of steps can be implemented as a workflow.
Example: Your First Workflow

The tools to build Workflows are installed by default in Medusa projects. For other Node.js projects, you can install the @medusajs/workflows-sdk package from npm.
Create a Step

A workflow is made of a series of steps. A step is created using the createStep utility function.

Create the file src/workflows/hello-world.ts with the following content:
src/workflows/hello-world.ts

import { 
  createStep,
  StepResponse,
} from "@medusajs/workflows-sdk"

const step1 = createStep("step-1", async () => {
  return new StepResponse(`Hello from step one!`)
})

This creates one step that returns a hello message.
Create a Workflow

Next, you can create a workflow using the createWorkflow function:
src/workflows/hello-world.ts

import {
  createStep,
  StepResponse,
  createWorkflow,
} from "@medusajs/workflows-sdk"

type WorkflowOutput = {
  message: string
}

const step1 = createStep("step-1", async () => {
  return new StepResponse(`Hello from step one!`)
})

const myWorkflow = createWorkflow<unknown, WorkflowOutput>(
  "hello-world",
  function () {
    const str1 = step1()

    return {
      message: str1,
    }
  }
)

export default myWorkflow

This creates a hello-world workflow. When you create a workflow, it’s constructed but not executed yet.
Execute the Workflow

You can execute a workflow from different places within Medusa.

    Use API Routes if you want the workflow to execute in response to an API request or a webhook.
    Use Subscribers if you want to execute a workflow when an event is triggered.
    Use Scheduled Jobs if you want your workflow to execute on a regular schedule.

To execute the workflow, invoke it passing the Medusa container as a parameter, then use its run method:

    API Route
    Subscribers
    Scheduled Job

src/api/store/custom/route.ts

import type { 
  MedusaRequest, 
  MedusaResponse
} from "@medusajs/medusa";
import myWorkflow from "../../../workflows/hello-world";

export async function GET(
  req: MedusaRequest, 
  res: MedusaResponse
) {
  const { result } = await myWorkflow(req.scope)
    .run()
    
  res.send(result)
}

If you run your backend and trigger the execution of the workflow (based on where you’re executing it), you should see the message Hello from step one!.
Pass Inputs to Steps

Steps in a workflow can accept parameters.

For example, create a new step that accepts an input and returns a message with that input:
src/workflows/hello-world.ts

type WorkflowInput = {
  name: string;
};

const step2 = createStep(
  "step-2", 
  async ({ name }: WorkflowInput) => {
    return new StepResponse(`Hello ${name} from step two!`)
  }
)

Then, update the workflow to accept input and pass it to the new step:
src/workflows/hello-world.ts

import {
  // previous imports 
  transform,
} from "@medusajs/workflows-sdk"

// ...

const myWorkflow = createWorkflow<
  WorkflowInput,
  WorkflowOutput
>("hello-world", function (input) {
  const str1 = step1()
  const str2 = step2(input)

  const result = transform(
    {
      str1,
      str2,
    },
    (input) => ({
      message: `${input.str1}\n${input.str2}`,
    })
  )

  return result
})

export default myWorkflow

Notice that to use the results of the steps, you must use the transform utility function. It gives you access to the real-time results of the steps once the workflow is executed.

Then, pass the necessary input to the workflow when you execute it. For example, in the API Route executing the workflow:
src/api/store/custom/route.ts

import type {
  MedusaRequest,
  MedusaResponse,
} from "@medusajs/medusa"
import myWorkflow from "../../../workflows/hello-world"

export async function GET(
  req: MedusaRequest,
  res: MedusaResponse
) {
  const { result } = await myWorkflow(req.scope).run({
    input: {
      name: req.query.name as string,
    },
  })

  res.send(result)
}

If you execute the workflow again, you’ll see:

    A Hello from step one! message, indicating that step one ran first.
    A Hello {name} from step two message, indicating that step two ran after.

Add Error Handling

Errors can occur in a workflow. To avoid data inconsistency, you can pass a compensation function as a third parameter to the createStep function.

The compensation function only runs if an error occurs throughout the Workflow. It’s useful to undo or roll back actions you’ve performed in a step.

For example, change step one to add a compensation function and step two to throw an error:
src/workflows/hello-world.ts

const step1 = createStep(
  "step-1",
  async () => {
    const message = `Hello from step one!`

    console.log(message)

    return new StepResponse(message)
  },
  async () => {
    console.log("Oops! Rolling back my changes...")
  }
)

const step2 = createStep(
  "step-2",
  async ({ name }: WorkflowInput) => {
    throw new Error("Throwing an error...")
  }
)

If you execute the Workflow, you should see:

    Hello from step one! logged, indicating that the first step ran successfully.
    Oops! Rolling back my changes... logged, indicating that the second step failed and the compensation function of the first step ran consequently.

Try it Out

You can try out this guide on Stackblitz.
More Advanced Example

Let’s cover a more realistic example.

For example, you can build a workflow that updates a product’s CMS details both in Medusa and an external CMS service:

import { createWorkflow } from "@medusajs/workflows-sdk"
import { Product } from "@medusajs/medusa" 
import { updateProduct, sendProductDataToCms } from "./steps"

type WorkflowInput = {
  id: string
  title: string,
  description: string,
  images: string[]
}

const updateProductCmsWorkflow = createWorkflow<
  WorkflowInput, 
  Product
>("update-product-cms", function (input) {
    const product = updateProduct(input)
    sendProductDataToCms(product)

    return product
})

As these steps are making changes to data in the Medusa backend and a third-party service, it’s useful to provide a compensation function for each step that rolls back the changes.

For example, you can pass a compensation function to the updateProduct step that reverts the product update in case an error occurs:

const updateProduct = createStep(
  "update-product", 
  async (input: WorkflowInput, context) => {
    const productService: ProductService = 
      context.container.resolve("productService")
    
    const { id, ...updateData } = input
    const previousProductData = await productService.retrieve(
      id, {
        select: ["title", "description", "images"],
      }
    )
    const product = await productService.update(id, updateData)

    return new StepResponse(product, {
      id,
      previousProductData,
    })
  }, async ({ id, previousProductData }, context) => {
    const productService: ProductService = 
      context.container.resolve("productService")

    await productService.update(id, previousProductData)
  }
)

Your steps may interact with external systems. For example, the sendProductDataToCms step communicates with an external CMS service. With the error handling and roll-back features that workflows provide, developers can ensure data delivery between multiple systems in their stack.
Constraints on Workflow Constructor Function

The Workflow Builder, createWorkflow, comes with a set of constraints:

    The function passed to the createWorkflow can’t be an arrow function:

// Don't
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", (input) => {
  // ...
  }
)

// Do
const myWorkflow = createWorkflow<WorkflowInput, WorkflowOutput>
  ("hello-world", function (input) {
  // ...
  }
)

    The function passed to the createWorkflow can’t be an asynchronous function.
    Since the constructor function only defines how the workflow works, you can’t directly manipulate data within the function. To do that, you must use the transform function:

// Don't
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", function (input) {
    const str1 = step1(input)
    const str2 = step2(input)
    
    return {
      message: `${input.str1}${input.str2}`,
    }
  }
)

// Do
const myWorkflow = createWorkflow<
  WorkflowInput, 
  WorkflowOutput
>("hello-world", function (input) {
    const str1 = step1(input)
    const str2 = step2(input)
    
    const result = transform({
      str1,
      str2,
    }, (input) => ({
      message: `${input.str1}${input.str2}`,
    }))

    return result
  }
) 
Workflows API Reference

This section of the documentation provides a reference to the utility functions of the @medusajs/workflows-sdk package.
Functions

    createStep
    createWorkflow
    parallelize
    transform