Medusa.js Custom Service Development
In Medusa 2.x, service is TypeScript class registered in IoC container, accessible via container.resolve(). Custom services encapsulate business logic, integrate external APIs, orchestrate modules, used in Workflows, Subscribers, API routes.
Service Types
| Type | Usage | Registration |
|---|---|---|
| Module Service | CRUD for module entities | Module() decorator |
| Custom Service | Business logic, orchestration | src/modules/*/service.ts |
| Workflow Step | Reusable step-logic | createStep() |
| Provider Service | Payment, fulfillment, files | Extends Abstract class |
Custom Service
type LoyaltyServiceDeps = {
logger: Logger;
};
export default class LoyaltyService {
protected logger: Logger;
constructor({ logger }: LoyaltyServiceDeps) {
this.logger = logger;
}
async getCustomerPoints(customerId: string): Promise<number> {
const db = // get connection via MikroORM
const result = await db.query<{ total: number }>(
`SELECT COALESCE(SUM(points), 0) as total
FROM loyalty_points
WHERE customer_id = $1 AND expires_at > NOW()`,
[customerId]
);
return result[0]?.total ?? 0;
}
async addPoints(data: LoyaltyPoint): Promise<void> {
this.logger.info(`Adding ${data.points} points to customer ${data.customerId}`);
await db.query(
`INSERT INTO loyalty_points (customer_id, points, reason, order_id, expires_at)
VALUES ($1, $2, $3, $4, NOW() + INTERVAL '1 year')`,
[data.customerId, data.points, data.reason, data.orderId ?? null]
);
}
}
Registration
export const LOYALTY_MODULE = 'loyaltyModuleService';
export default Module(LOYALTY_MODULE, {
service: LoyaltyService,
});
Using in Workflow
const addLoyaltyPointsStep = createStep(
'add-loyalty-points',
async (input, context) => {
const loyaltyService: LoyaltyService = context.container.resolve(LOYALTY_MODULE);
const pointsToAdd = Math.floor(input.orderTotal / 100);
await loyaltyService.addPoints({
customerId: input.customerId,
points: pointsToAdd,
reason: 'order_completed',
});
return new StepResponse({ pointsAdded: pointsToAdd });
}
);
Using in API Route
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const session = req.session as { customer_id?: string };
if (!session.customer_id) return res.status(401).json({ message: 'Unauthorized' });
const loyaltyService: LoyaltyService = req.scope.resolve(LOYALTY_MODULE);
const points = await loyaltyService.getCustomerPoints(session.customer_id);
res.json({ points, customer_id: session.customer_id });
};
Timeline
- Simple service (get/write data, 1–2 operations): 1–2 days
- Service with external API, retry logic, tests: 3–5 days
- Complex service (loyalty, B2B pricing, custom inventory): 1–3 weeks







