Adapters
Medusa
Medusa V2 platform adapter — maps the Medusa Store API to the unified CommerceAdapter interface.
The @commercejs/adapter-medusa package implements the CommerceAdapter interface for Medusa, the open-source headless commerce platform. It targets the Medusa V2 Store API.
Installation
pnpm add @commercejs/adapter-medusa
Configuration
import { MedusaAdapter } from '@commercejs/adapter-medusa'
const adapter = new MedusaAdapter({
baseUrl: 'http://localhost:9000',
publishableApiKey: process.env.MEDUSA_PUBLISHABLE_KEY!,
defaultRegionId: 'reg_01J...', // optional, used for cart creation
})
| Option | Type | Required | Description |
|---|---|---|---|
baseUrl | string | Yes | Your Medusa server URL |
publishableApiKey | string | Yes | Publishable API key from Medusa admin |
defaultRegionId | string | No | Default region for cart creation |
apiToken | string | No | JWT token for authenticated endpoints |
timeout | number | No | Request timeout in ms (default: 30000) |
Supported Domains
| Domain | Methods | Status |
|---|---|---|
| Catalog | getProducts, getProduct, getCategories | Implemented |
| Cart | createCart, getCart, addToCart, updateCartItem, removeFromCart, applyCoupon | Implemented |
| Checkout | getShippingMethods, setShippingAddress, setBillingAddress, setShippingMethod, setPaymentMethod, placeOrder | Implemented |
| Customer | login, register, logout, getCustomer, updateCustomer | Implemented |
| Order | getOrder, getCustomerOrders | Implemented |
| Store | getStoreInfo | Implemented |
| Country | getCountries | Implemented |
Data Mapping
The adapter transforms Medusa V2 Store API responses into unified CommerceJS types.
Product Mapping
| Medusa Field | CommerceJS Field | Notes |
|---|---|---|
title | name | Wrapped in LocalizedString |
handle | slug | URL-friendly identifier |
variants[].calculated_price | price | Converted from minor units (cents → dollars) |
images[].url | images[].url | Mapped to Image type |
options[].values | options | Mapped to ProductOption |
variants | variants | Always present in Medusa products |
Order Status Mapping
| Medusa Status | CommerceJS Status |
|---|---|
pending | pending |
completed | delivered |
archived | delivered |
canceled | cancelled |
requires_action | processing |
Price Conversion
Medusa stores all prices in minor units (cents). The adapter automatically converts:
// Medusa: { calculated_amount: 2999, currency_code: 'usd' }
// CommerceJS: { amount: 29.99, currency: 'USD', formatted: '$29.99' }
Usage Examples
Fetch Products
const results = await adapter.getProducts({ query: 'shirt' })
// Returns SearchResult with products, facets, pagination
Cart + Checkout Flow
// Create cart
const cart = await adapter.createCart()
// Add item (variantId is required for Medusa)
await adapter.addToCart(cart.id, {
productId: 'prod_01',
variantId: 'var_01',
quantity: 2,
})
// Set shipping address
await adapter.setShippingAddress(cart.id, {
firstName: 'Ahmed',
lastName: 'Ali',
street: '123 Main St',
city: 'Riyadh',
country: 'SA',
postalCode: '12345',
})
// Get shipping options and select one
const methods = await adapter.getShippingMethods(cart.id)
await adapter.setShippingMethod(cart.id, methods[0].id)
// Place order (completes the cart)
const order = await adapter.placeOrder(cart.id)
Customer Authentication
// Login
const customer = await adapter.login('user@example.com', 'password')
// Register
const newCustomer = await adapter.register({
email: 'new@example.com',
password: 'password',
firstName: 'Test',
lastName: 'User',
})
Medusa vs Salla
Key differences between the two adapters:
| Aspect | Medusa | Salla |
|---|---|---|
| Cart | Full CRUD + API checkout | Hosted checkout only |
| Auth | JWT via /auth/customer/emailpass | OAuth access tokens |
| Variants | Always required | Optional |
| Prices | Minor units (cents) | Major units (10.00) |
| Store Info | Derived from /regions | Direct /store/info endpoint |
| Pagination | offset/limit | page/per_page |
Error Handling
The adapter wraps Medusa API errors in CommerceError:
import { CommerceError } from '@commercejs/types'
try {
await adapter.addToCart(cartId, { productId: 'prod_01', quantity: 1 })
} catch (err) {
if (err instanceof CommerceError) {
// err.code === 'VALIDATION' (missing variantId)
// err.statusCode === 400
}
}
Unsupported domains (wishlist, reviews, promotions, etc.) throw CommerceError with code NOT_SUPPORTED and status 501.