Framework

icon

Framework

Build any feature, 
integrate with any system 

A built-in backend framework that lets you create customizations and integrations in hours, not weeks. 
blueprint framework

With the Medusa framework, the only limit is your imagination.

  • Sync product data from ERP
  • Inject custom price data to cart
  • Extend data schemas
  • Add custom endpoints
  • Hook into existing workflows
  • Manage complex storefront queries
  • Build custom data models
  • Listen to webhooks from 3rd-party tools
  • Create advanced automations
  • Add human-in-the-loop workflows

Workflows

Orchestrate actions across systems with Workflows

Traditional commerce platforms force you to use separate servers, middleware, or applications for customizations. This is hard to manage, leading to long implementations and unreliable operations. In Medusa, you have a built-in foundation to build customizations natively.

Our tools enable you to avoid hacky workarounds and instead build bespoke, scalable commerce applications fast. Let Medusa provide the framework, so you can focus on building the features that differentiate your business.

A workflow step performs a query or action in a system. Steps can receive inputs from previous steps and return data to be used in subsequent steps. You can resolve services in your steps allowing you to build business logic involving Medusa’s core commerce modules or your 3rd party or custom systems.

create-order-in-erp.ts

sync-order-to-erp.ts

src > workflows > steps > create-order-in-erp.ts

import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
export const createOrderInERP = createStep(
"create-order-in-erp"
async (order: StepInput, { container }) => {
const erpService = container.resolve("erp")
const createdOrder = await erpService.createOrder(order)
return new StepResponse(createdOrder)
}
)

To connect multiple steps together you create a Workflow. Creating a Workflow is like writing any other function. Behind the scenes Medusa generates a representation of your workflow that enables automatic retries of steps if they fail.

create-order-in-erp.ts

sync-order-to-erp.ts

src > workflows > sync-order-to-erp.ts

import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createOrderInERP, transformOrder } from "./steps"
export const syncOrderToERP = createWorkflow(
"sync-order-to-erp",
(input: WorkflowInput, { container }) => {
const medusaOrder = retrieveOrder(input.order_id)
const createdOrder = when({ medusaOrder }, ({ medusaOrder }) => {
return medusaOrder.status === "completed"
}).then(() => {
const preparedOrder = transformOrder(medusaOrder)
return createOrderInERP(preparedOrder)
})
return new WorkflowResponse(createdOrder)
}
)

If step retries don't succeed, Medusa will rollback previous steps. A rollback will call a step's compensation method, ensuring your data is kept consistent across systems.

create-order-in-erp.ts

sync-order-to-erp.ts

src > workflows > steps > create-order-in-erp.ts

import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
export const createOrderInERP = createStep(
"create-order-in-erp"
async (order: StepInput, { container }) => {
const erpService = container.resolve("erp")
const createdOrder = await erpService.createOrder(order)
return new StepResponse(createdOrder, createdOrder)
},
// In the face of errors, we roll back and delete the order
async (createdOrder, { container }) => {
const erpService = container.resolve("erp")
await erpService.deleteOrder(createdOrder)
}
)

You can trigger your workflows anywhere in Medusa. Use your workflows in Subscribers to start them in response to an event. Start them in response to requests with API Routes. Or have them run on a defined schedule with Scheduled Jobs.

create-order-in-erp.ts

sync-order-to-erp.ts

src > subscribers > sync-order-to-erp.ts

import {
type SubscriberConfig,
} from "@medusajs/framework"
import syncOrderToErp from "../workflows/sync-order-to-erp"
export default async function handleOrderPlaced({
event: { data },
container,
}) {
const orderService = container.resolve("order")
const order = await orderService.retrieve(id)
await syncOrderToErp(container).run({
input: {
order
},
})
}
export const config: SubscriberConfig = {
event: "order.placed",
}

Modules

Build custom modules to support any commerce logic

Unlike other platforms that require separate applications for customization, Medusa enables you to write custom modules that integrate seamlessly into your application.

You can define your own data models, endpoints, and logic for modules that work alongside Medusa's pre-built commerce modules.

A data model represents a table in the database. You create data models using Medusa's Data Model API. It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations.

company.ts

service.ts

create-company.ts

route.ts

src > modules > company > models > company.ts

import { model } from "@medusajs/framework/utils"
export const Company = model.define("company", {
id: model.id({ prefix: "comp" }).primaryKey(),
name: model.text(),
logo_url: model.text().nullable(),
domain: model.text().nullable(),
employees: model.hasMany(() => Employee),
spending_limit_reset_frequency: model
.enum(["never", "daily", "weekly", "monthly", "yearly"])
.default("monthly"),
})
export const Employee = model.define("employee", {
id: model.id({ prefix: "emp" }).primaryKey(),
email: model.text(),
spending_limit: model.bigNumber().default(0),
company: model.belongsTo(() => Company, {
mappedBy: "employees",
}),
});

You perform database operations on your data models in a service. Services provide methods, such as create, update, retrieve, list, and more, to manage your data models out of the box. Additionally, services can hold custom methods for your business logic and/or integrate with third-party providers.

company.ts

service.ts

create-company.ts

route.ts

src > modules > company > service.ts

import { MedusaService } from "@medusajs/framework/utils"
import { Company, Employee } from "./models"
class CompanyModuleService extends MedusaService({
Company,
Employee
}) {
async validateEmployeeEmail(employee, company) {
if (!employee.email.includes(company.domain)) {
throw new Error("Employee email not associated with company")
}
}
}
export default CompanyModuleService

Once services are defined, they can be resolved from Medusa's dependency container in API Routes, Subscribers, Scheduled Jobs, Workflows, and more.

company.ts

service.ts

create-company.ts

route.ts

src > workflows > steps > companies > create-company.ts

import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
export const createCompanyStep = createStep(
"create-company-step",
async (input: CreateCompanyStepInput, { container }) => {
const companyService = container.resolve("company")
const company = await companyService.createCompanies(input)
return new StepResponse(company)
}
)

With multiple modules defined and linked to each other, you can use Query to retrieve data across all of them, including third-party systems. Similar to services, Query can be resolved from Medusa's dependency container across all the different application entry points.

company.ts

service.ts

create-company.ts

route.ts

src > api > admin > companies > route.ts

import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
export const POST = async (
req: MedusaRequest,
res: MedusaResponse
) => {
const query = req.scope.resolve("query")
const { data } = await query({
entity: "company",
fields: ["id", "name", "employees"],
filters: {
take: 20,
skip: 0
}
})
res.json({ companies: data })
}

Query

Query data across multiple systems.

Query gives you a single entry point to data across all systems in your stack. Create data relationships between modules with the new Link Modules and write queries that fetch data from multiple sources, including 3rd-party systems.

index engine illustration

Admin extensions

Powerful system for Admin extensions.

Extend existing Admin pages by adding widgets, which let you insert custom React components into predefined injection zones. Or go all the way and create new bespoke Admin pages using UI routes.

Widgets allow you to add custom React components in injection zones spread across existing pages in the Admin Dashboard. For example, you can add a button on the order details page that links to the order in an integrated ERP system. Widgets are easy to build, and Medusa automatically registers them when exporting a React component and its configuration from files placed in your Medusa project.

erp-button.tsx

page.tsx

src > admin > widgets > erp-button.tsx

import type { WidgetConfig } from "@medusajs/admin"
import { Container } from "@medusajs/ui"
const OrderWidget = ({ order }) => {
const link = `https://myerp.com/orders/${order.id}`
return (
<Container className="divide-y p-0">
<a href={link}>
View in MyERP
</a>
</Container>
)
}
export const config = defineWidgetConfig({
zone: "order.details.before",
})
export default OrderWidget

UI Routes allow you to add new routes to your admin dashboard. Similar to Widgets, Admin UI Routes are simply exported React components created in files in your Medusa project. UI Routes can be added to the sidebar as new items or sub-items of existing routes.

erp-button.tsx

page.tsx

src > admin > routes > erp-orders > page.tsx

import { defineRouteConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
const ERPOrdersPage = () => {
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
<Heading level="h1">Nested Orders Page</Heading>
</div>
</Container>
)
}
export const config = defineRouteConfig({
label: "ERP Orders",
nested: "/orders",
})
export default ERPOrdersPage

Admin extensions

Powerful system for Admin extensions and customizations.

Support your custom logic and unique business workflows with a powerful extension system and native UI.

image
icon

Recipes

Build any commerce application.

Want to get started but not sure where to begin? Use some of our recipes to build your own custom use case

Ready to build your custom commerce setup?