# How to Extend Medusa's Core Functionality: A Complete Guide for Developers

> Extend Medusa's core functionality with custom modules, Workflows SDK, and new API routes. Learn how to enhance your e-commerce platform without core code changes.

- Repository: [Medusa/medusa](https://github.com/medusajs/medusa)
- Tags: how-to-guide
- Published: 2026-05-19

---

**You can extend Medusa's core functionality by creating custom modules that override services through the dependency-injection container, implement workflows using the Workflows SDK, and expose new API routes without modifying the core source code.**

Medusa is architected as a modular TypeScript monorepo where commerce modules, workflows, and API routes remain loosely coupled through a dependency-injection container. To extend Medusa's core functionality, you leverage the same internal patterns the platform uses—service decorators, container registrations, and workflow steps—to safely augment or replace existing business logic.

## Understanding Medusa's Extension Architecture

Medusa's core runtime relies on **dependency injection** to wire together services, repositories, and API handlers. The framework registers these components using keys defined in `ContainerRegistrationKeys` (located in [`packages/core/framework/src/container.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/framework/src/container.ts)).

When you extend functionality, you interact with three primary layers:

- **Services**: Classes extending `MedusaService` that contain business logic
- **Workflows**: Composable chains of steps defined using `createWorkflow` in [`packages/core/workflows-sdk/src/create-workflow.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/workflows-sdk/src/create-workflow.ts)
- **API Routes**: HTTP handlers that resolve services from the container via `req.scope.resolve()`

## Core Extension Mechanisms

The Medusa codebase exposes several extension points you can leverage:

| Extension Point | Purpose | Source Reference |
|----------------|---------|------------------|
| **Service Override** | Replace or augment module services while maintaining the public contract | [`packages/modules/order/src/services/order-module-service.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/order/src/services/order-module-service.ts) |
| **Custom Workflow** | Define new business processes using composable steps | [`packages/core/workflows-sdk/src/create-workflow.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/workflows-sdk/src/create-workflow.ts) |
| **API Route** | Add HTTP handlers under Admin or Store APIs | [`packages/medusa/src/api/admin/orders/route.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/admin/orders/route.ts) |
| **Plugin Registration** | Load code at startup via the plugin system | [`packages/plugins/loyalty/src/plugin.ts`](https://github.com/medusajs/medusa/blob/main/packages/plugins/loyalty/src/plugin.ts) |
| **Database Migration** | Modify schema with automated migration runs | [`packages/plugins/loyalty/src/modules/store-credit/migrations/Migration20250722080351.ts`](https://github.com/medusajs/medusa/blob/main/packages/plugins/loyalty/src/modules/store-credit/migrations/Migration20250722080351.ts) |

## Step-by-Step Implementation Guide

### 1. Create a New Module or Plugin

Start by creating a new directory under `packages/modules/` or `packages/plugins/`. Every extension requires an entry point that exports a plugin configuration object.

```typescript
// packages/modules/my-custom/src/index.ts
import { Plugin } from "@medusajs/framework/utils"

export default {
  load: async ({ container }) => {
    // Registration logic executes at startup
    console.log("Custom module loaded")
  },
} as Plugin

```

This pattern mirrors official plugins like the loyalty plugin in [`packages/plugins/loyalty/src/index.ts`](https://github.com/medusajs/medusa/blob/main/packages/plugins/loyalty/src/index.ts).

### 2. Override Core Services with Decorators

To modify existing behavior, extend the base service class and use Medusa's decorators for dependency injection and event emission. The `@InjectManager` decorator (defined in [`packages/core/utils/src/decorators.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/utils/src/decorators.ts)) automatically injects the transactional entity manager.

```typescript
// packages/modules/my-custom/src/services/custom-order-service.ts
import {
  MedusaService,
  InjectManager,
  EmitEvents,
  MedusaContext,
} from "@medusajs/framework/utils"
import type { IOrderModuleService, Context } from "@medusajs/framework/types"

export class CustomOrderService
  extends MedusaService<{ Order: { dto: any } }>({ Order })
  implements IOrderModuleService
{
  @InjectManager()
  @EmitEvents()
  async cancelOrder(
    orderId: string,
    @MedusaContext() ctx: Context = {}
  ) {
    // Custom logic before core cancellation
    console.log(`[Custom] Processing cancellation for ${orderId}`)
    
    // Call original implementation
    await this.orderService_.cancelOrder(orderId, ctx)
    
    // Post-cancellation logic
    await this.sendCancellationWebhook(orderId)
  }
}

```

This implementation follows the same decorator pattern used in the core `OrderModuleService` at [`packages/modules/order/src/services/order-module-service.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/order/src/services/order-module-service.ts).

### 3. Register Services in the DI Container

Override the default service implementation by registering your custom class in the container during the plugin's `load` hook. Medusa uses **Awilix** for dependency injection.

```typescript
// packages/modules/my-custom/src/plugin.ts
import { asClass } from "awilix"
import { CustomOrderService } from "./services/custom-order-service"

export default {
  load: async ({ container }) => {
    container.register(
      "orderService",
      asClass(CustomOrderService).singleton()
    )
  },
}

```

The container resolves this key whenever `req.scope.resolve("orderService")` is called throughout the application.

### 4. Build Custom Workflows

Encapsulate complex business logic in reusable workflows using the Workflows SDK. Workflows consist of steps created via `createStep` in [`packages/core/workflows-sdk/src/create-step.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/workflows-sdk/src/create-step.ts).

```typescript
// packages/modules/my-custom/src/workflows/notification-workflow.ts
import {
  createWorkflow,
  WorkflowData,
  createStep,
  WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"

export const notifyStep = createStep(
  "notify-admin",
  async (input: { orderId: string }, { container }) => {
    const notificationService = container.resolve("notificationService")
    await notificationService.sendOrderUpdate(input.orderId)
    return { success: true }
  }
)

export const notificationWorkflow = createWorkflow(
  "send-notification",
  (input: WorkflowData<{ orderId: string }>) => {
    notifyStep(input)
    return new WorkflowResponse({ notified: true })
  }
)

```

### 5. Add Custom API Routes

Expose your workflows or services through new HTTP endpoints. Routes follow the Next.js App Router convention and receive typed request and response objects.

```typescript
// packages/medusa/src/api/admin/orders/notify/route.ts
import {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"
import { notificationWorkflow } from "@my-custom/workflows/notification-workflow"

export const POST = async (
  req: AuthenticatedMedusaRequest,
  res: MedusaResponse
) => {
  const { id } = req.params
  
  await notificationWorkflow(req.scope).run({
    input: { orderId: id }
  })
  
  res.status(200).json({ message: "Notification sent" })
}

```

This route structure aligns with core admin routes in [`packages/medusa/src/api/admin/orders/route.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/admin/orders/route.ts).

### 6. Database Migrations

If your extension requires new tables or columns, create a migration class extending `Migration` from `@medusajs/framework/types`.

```typescript
// packages/modules/my-custom/migrations/1712345678900-add-audit-log.ts
import { Migration } from "@medusajs/framework/types"

export class Migration1712345678900 extends Migration {
  async up(queryRunner) {
    await queryRunner.query(`
      CREATE TABLE audit_log (
        id varchar PRIMARY KEY,
        action varchar NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )
    `)
  }

  async down(queryRunner) {
    await queryRunner.query(`DROP TABLE audit_log`)
  }
}

```

Medusa automatically detects and runs migrations from `packages/*/migrations/*.ts` on startup.

## Complete Working Example

Here is a minimal, runnable extension that combines service overrides, workflows, and API routes:

**Service Implementation:**

```typescript
// src/services/enhanced-cart-service.ts
import { MedusaService, InjectManager } from "@medusajs/framework/utils"

export class EnhancedCartService extends MedusaService {
  @InjectManager()
  async customCalculation(cartId: string, @MedusaContext() ctx: Context) {
    const cart = await this.retrieve(cartId, {}, ctx)
    // Custom calculation logic
    return { subtotal: cart.subtotal * 0.95 } // 5% automatic discount
  }
}

```

**Plugin Registration:**

```typescript
// src/index.ts
import { asClass } from "awilix"
import { EnhancedCartService } from "./services/enhanced-cart-service"

export default {
  load: async ({ container }) => {
    container.register(
      "cartService",
      asClass(EnhancedCartService).singleton()
    )
  },
}

```

**Workflow Integration:**

```typescript
// src/workflows/calculate-workflow.ts
import { createWorkflow, createStep } from "@medusajs/framework/workflows-sdk"

const calculateStep = createStep(
  "calculate-totals",
  async ({ cartId }: { cartId: string }, { container }) => {
    const service = container.resolve("cartService")
    return await service.customCalculation(cartId)
  }
)

export const calculateWorkflow = createWorkflow(
  "calculate-cart",
  (input: { cartId: string }) => {
    const result = calculateStep(input)
    return new WorkflowResponse(result)
  }
)

```

## Summary

Extending Medusa's core functionality follows established architectural patterns:

- **Override services** by extending `MedusaService` and using decorators like `@InjectManager` and `@EmitEvents`
- **Register components** in the DI container via plugin `load` hooks using Awilix
- **Build workflows** with `createStep` and `createWorkflow` from the Workflows SDK
- **Expose HTTP endpoints** by adding routes under `packages/medusa/src/api/`
- **Manage schema changes** through migration classes extending `Migration`

These patterns ensure your extensions remain compatible with core updates and follow Medusa's modular philosophy.

## Frequently Asked Questions

### How do I override an existing service without breaking core updates?

Extend the base service class rather than modifying source files. Register your implementation in the container using the same service key (e.g., `"orderService"`). Medusa resolves the last registered implementation, allowing your code to intercept calls while maintaining the original interface contract.

### Can I extend Medusa's functionality without creating a plugin?

Yes. You can register custom services directly in your Medusa server configuration or within the `src` directory of your Medusa project. However, packaging extensions as modules or plugins in `packages/modules/` provides better organization and reusability across projects.

### What is the difference between `@InjectManager` and `@InjectTransactionManager`?

`@InjectManager` (from [`packages/core/utils/src/decorators.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/utils/src/decorators.ts)) injects the EntityManager for database operations, while `@InjectTransactionManager` specifically handles transactional contexts. Use `@InjectManager` for standard queries and `@EmitEvents` when your service method should trigger event bus notifications after completion.

### How do I test my custom workflows and services?

Run the integration test suite using `yarn test:integration:modules` from the repository root. For API routes, add tests in `packages/medusa/integration-tests/api/` using Medusa's `setupServer` and `setupDatabase` helpers to instantiate a test environment with your extensions loaded.