Medusa - Framework
Framework
Framework
Build any feature, integrate with any system
A built-in backend framework that lets you create customizations and integrations in hours, not weeks.
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
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.
Define a step
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, third-party systems, or custom systems.
Define a workflow
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.
Rollback on permanent failures
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.
Run your workflow
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.
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)
}
)
Define a step
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)
}
)
Define a workflow
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)
}
)
Rollback on permanent failures
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)
}
)
Run your workflow
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.
Data Model
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.
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",
}),
});
Service
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.
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
Container
Once services are defined, they can be resolved from Medusa's dependency container in API Routes, Subscribers, Scheduled Jobs, Workflows, and more.
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)
}
)
Query
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.
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 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.
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.
UI Widgets
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.
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
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.
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