Skip to main content

How to Create a Module

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

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


(Optional) Step 0: Project Preparation

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

custom-module
|
|___ index.ts
|
|___ services // directory
Report Incorrect CodeCopy to Clipboard

The directory can be an NPM project, but that is optional. index.tsCopy to Clipboard acts as an entry point to your Module. You'll learn about its content in a later step. The serviceCopy to Clipboard 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 modelsCopy to Clipboard directory.

You can use JavaScript instead of TypeScript.

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

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

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.tsCopy to Clipboard or index.jsCopy to Clipboard 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-moduleCopy to Clipboard.

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.


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.tsCopy to Clipboard.

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>
}
Report Incorrect CodeCopy to Clipboard

All property types such as ModuleLoaderFunctionCopy to Clipboard can be loaded from the @medusajs/modules-sdkCopy to Clipboard package.

Where:

  • serviceCopy to Clipboard: 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 ICacheServiceCopy to Clipboard interface exported from @medusajs/typesCopy to Clipboard.
  • loadersCopy to Clipboard: (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.
  • migrationsCopy to Clipboard: (optional) an array of objects containing database migrations that should run when the migrationCopy to Clipboard command is used with Medusa's CLI.
  • modelsCopy to Clipboard: (optional) an array of entities that your module creates.
  • runMigrationsCopy to Clipboard: (optional) a function that can be used to define migrations to run when the migration runCopy to Clipboard 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.
  • revertMigrationCopy to Clipboard: (optional) a function can be used to define how migrations should be reverted when the migration revertCopy to Clipboard 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.tsCopy to Clipboard 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
Report Incorrect CodeCopy to Clipboard

Step 3: Reference Module

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

medusa-config.js
module.exports = {
// ...
modules: {
// ...
moduleType: {
resolve: "module-name-or-path",
options: {
// options if necessary
},
// optional
resources: "shared",
},
},
}
Report Incorrect CodeCopy to Clipboard

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

  • moduleTypeCopy to Clipboard is the type of your module. For example, if your module is a cache module, it should be changed to cacheServiceCopy to Clipboard.
  • resolveCopy to Clipboard 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.
  • optionsCopy to Clipboard should hold any options of your module, if necessary.
  • resourcesCopy to Clipboard 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 resolveCopy to Clipboard property should be the name of that package. For example:

medusa-config.js
module.exports = {
// ...
modules: {
// ...
moduleType: {
resolve: "custom-module",
// ...
},
},
}
Report Incorrect CodeCopy to Clipboard

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

For example, consider you have the following file structure:

|
|___ custom-module
| |
| |___ index.ts
| |
| |___ services
| | |
| | |___ custom-service.ts
| |___ // more files
|
|
|___ medusa-backend
Report Incorrect CodeCopy to Clipboard

You can reference your module in two ways:

1. Referencing the directory: In this case, it's assumed that the index.tsCopy to Clipboard 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",
// ...
},
},
}
Report Incorrect CodeCopy to Clipboard

2. Referencing indexCopy to Clipboard file: In this case, it's assumed that the index.tsCopy to Clipboard or index.jsCopy to Clipboard 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/index.ts",
// ...
},
},
}
Report Incorrect CodeCopy to Clipboard

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 resourcesCopy to Clipboard property available as part of the module's configurations:

medusa-config.js
module.exports = {
// ...
modules: {
// ...
moduleType: {
// other configurations
resources: "shared",
},
},
}
Report Incorrect CodeCopy to Clipboard

The resourcesCopy to Clipboard property can have one of the following values:

  • sharedCopy to Clipboard: (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.
  • isolatedCopy to Clipboard: the module receives an empty dependency container, and only its own dependencies will be registered in the container. When using this value, you must establish the database connection yourself and managing other resources within your module.

Step 4: Test Your Module

Finally, to test your module, run the following command:

npx @medusajs/medusa-cli develop
Report Incorrect CodeCopy to Clipboard

This starts the Medusa backend and runs your module as part of it.


Next Steps

After you finish developing your module, you can publish it as an NPM package with this guide.

Was this page helpful?