Medusa.js Custom Module Development
In Medusa 2.x, concept of "plugin" transformed to Medusa Module — independent package with own models, services, migrations. Modules published to npm and connected via medusa-config.ts. This differs from v1 where plugins more monolithic.
Module Structure
packages/my-module/
├── src/
│ ├── index.ts # Entry point
│ ├── models/ # MikroORM entities
│ │ └── custom-item.ts
│ ├── services/ # Business logic
│ ├── migrations/ # DB migrations
│ └── types/ # TypeScript types
└── dist/ # Compiled JS
Model Definition
const CustomItem = model.define('custom_item', {
id: model.id().primaryKey(),
name: model.text(),
sku: model.text().unique(),
metadata: model.json().nullable(),
is_active: model.boolean().default(true),
created_at: model.dateTime(),
updated_at: model.dateTime(),
});
Module Service
class CustomItemModuleService extends MedusaService({
CustomItem,
}) {
async listActiveByProduct(productId: string) {
return await this.listCustomItems({
product_id: productId,
is_active: true,
});
}
}
Module Entry Point
import { Module } from '@medusajs/framework/utils';
import CustomItemModuleService from './services/custom-item';
export const CUSTOM_ITEM_MODULE = 'customItem';
export default Module(CUSTOM_ITEM_MODULE, {
service: CustomItemModuleService,
});
Connect in medusa-config.ts
export default defineConfig({
modules: [
{
resolve: './packages/my-module/src',
options: {
apiEndpoint: process.env.CUSTOM_API_ENDPOINT,
},
},
],
});
Timeline
- Simple module with CRUD and API routes: 2–4 days
- Module with external API integration: 5–10 days
- Complex module with workflows and migrations: 2–3 weeks







