Skip to content

medusa API

Everything the package exports, by the path you import it from. Open an export for its signature and options.

@evolve-framework/medusa

The Medusa client and its configuration, the request context, error mapping and slug helpers.

Classes

ClientContextThe signed-in customer for one request, if there is one.
import { ClientContext } from "@evolve-framework/medusa";

The Medusa access token lives inside the platform's federated token rather than alongside it, so the storefront and gateway keep speaking one session format regardless of which commerce engine is behind them.

new ClientContext(storeContext: StoreContext, clientFactory: ClientFactory, token?: FederatedToken, loader?: ClientContextLoader)
OptionTypeDescription
getClientInfo(clientContext: ClientContext$1) => Promise<ClientContextInfo | undefined>
MethodDescription
clientInfo(): Promise<ClientContextInfo | undefined>Who the session belongs to, read once per request; undefined for a guest.
isAuthenticated(): boolean
setAccessToken(serialised: string): voidAdopt a session arriving on the request.
load(): Promise<void>Settle the session for this request.
setSession(session: MedusaSession): voidRecord a freshly issued Medusa session on the federated token.
clearSession(): void
setActiveCart(cartId: string | undefined): void
ensureWishlistAnonymousId(): stringThe anonymous wishlist owner, minting one if the shopper has none.
servesStore(storeKey: string): booleanWhether this service serves a store, so a business context may name it.
setActiveBusinessUnit( businessUnitId: string | undefined, storeKey?: string, ): void
startGuestSession(): booleanOpen a guest session, unless a session of any kind is already there.
recordGuestOrder(orderId: string): void
clearGuestOrders(): void
clearWishlistAnonymousId(): void
clientFor(accessToken: string): MedusaA client acting as the bearer of a specific token.
requireUserClient(): MedusaA client acting as the signed-in customer, or a typed error explaining why there is none.
ClientFactoryBuilds Medusa clients.
import { ClientFactory } from "@evolve-framework/medusa";

The locale is fixed at construction rather than set afterwards: the SDK's setLocale() is a no-op outside the browser and logs a warning telling you to send the x-medusa-locale header instead. Since a client therefore carries exactly one locale, clients are keyed by store and locale.

System clients are cached because they are pure configuration and shared by every anonymous request. User clients never are: they carry a customer's token, and a cache keyed loosely enough to be useful would be a cache that can serve one customer's client to another.

new ClientFactory()
MethodDescription
configure(options: ClientFactoryOptions): void
supportedCurrencies(storeKey: string): Promise<string[]>The currencies a store prices in, read from its regions on first use.
supportedLocales(storeKey: string): Promise<string[]>The locales a store serves, read from Medusa on first use.
storeKeys(): string[]The keys of every store this factory is configured for.
storeConfig(storeKey: string): MedusaStoreConfig
getSystemRequestBuilder(storeKey: string, locale: string): Medusa
createUserRequestBuilder( storeKey: string, locale: string, accessToken: string, ): UserRequestBuilder
MedusaClientContextLoaderReads the signed-in customer from Medusa's store API with the session's own token: the default, for a service that talks to Medusa itself.
import { MedusaClientContextLoader } from "@evolve-framework/medusa";
new MedusaClientContextLoader()
MethodDescription
getClientInfo( clientContext: ClientContext$1, ): Promise<ClientContextInfo | undefined>
MedusaConfigEverything the adapter needs to talk to Medusa — and nothing that would let it act as an administrator.
import { MedusaConfig } from "@evolve-framework/medusa";

The Store API and a publishable key are the whole surface. Where the Store API does not expose something a storefront legitimately needs, the answer is a Medusa plugin adding a store route, not admin credentials held by a public-facing service: credentials in reach of a request are credentials that can leak through one, and they would grant far more than the operation asked for.

Extends BaseConfig

new MedusaConfig()
RemoteClientContextLoaderAsks another service's /api/client-context instead, for a service that holds the federated token but does not talk to Medusa.
import { RemoteClientContextLoader } from "@evolve-framework/medusa";
new RemoteClientContextLoader(endpoint: string)
MethodDescription
getClientInfo( clientContext: ClientContext$1, ): Promise<ClientContextInfo | undefined>
StoreContextThe storefront context for one request: which store, in which language, in which currency.
import { StoreContext } from "@evolve-framework/medusa";

Locale is its own axis here, not a property of the region. nl-BE and fr-BE share a country and a currency and differ only in language, so deriving one from the other would make Belgium unrepresentable.

new StoreContext(args: StoreContextArgs, client: Medusa)
OptionTypeDescription
storeKeystring
localestring
currencystring
MethodDescription
taxCountry(): string | undefinedThe country a price is taxed in: the locale's region, such as nl for nl-NL.
regionId(): Promise<string>The Medusa region matching this context's currency.

Functions

clearRegionCacheExposed for tests; regions are otherwise cached for the process lifetime.
import { clearRegionCache } from "@evolve-framework/medusa";
function clearRegionCache(): void

Returns void

commerceError
import { commerceError } from "@evolve-framework/medusa";
function commerceError(
message: string,
code: ErrorCodeValue,
extensions?: Record<string, unknown>,
): GraphQLError

Returns GraphQLError

getClientOptionsTranslate configuration into the options a ClientFactory needs.
import { getClientOptions } from "@evolve-framework/medusa";
function getClientOptions(
config: Pick<MedusaConfig, "MEDUSA_API_URL" | "MEDUSA_STORES" | "MEDUSA_REQUEST_TIMEOUT">,
): ClientFactoryOptions
OptionTypeDescription
MEDUSA_API_URLstring

The Medusa server's URL, the base of its Store API.

MEDUSA_STORESMedusaStoreConfigs

The stores this service serves, as JSON keyed by store key, each with its publishableKey. A store without one fails the load.

MEDUSA_REQUEST_TIMEOUTnumber

Milliseconds a request to Medusa may take, reading the response included, before it is aborted with a TimeoutError. Default 10000; 0 means no limit.

Returns ClientFactoryOptions

listSlugsEvery locale's slug for a set of resources, in one request.
import { listSlugs } from "@evolve-framework/medusa";
function listSlugs(
client: Medusa,
reference: SlugReference,
referenceIds: readonly string[],
): Promise<EntitySlug[]>

Returns Promise<EntitySlug[]>

pickupPointListPickup points from a fixed list.
import { pickupPointList } from "@evolve-framework/medusa";
function pickupPointList(options: PickupPointListOptions): PickupPointProvider
OptionTypeDescription
pointsPickupPointEntry[]
shippingMethodsRecord<string, string>

Shipping method key per carrier key; a carrier without one offers no points.

timeZone?string

The zone opening hours are read in. Defaults to Europe/Amsterdam.

leadDays?number

Days between ordering and a parcel arriving at the point. Defaults to 1.

readyAt?string

Local time a parcel is ready on the day it arrives. Defaults to 12:00.

Returns PickupPointProvider

shippingMethodScheduleDelivery dates from the cart's shipping options: each option the schedule names by its type code is a carrier delivering in its windows on its weekdays at that option's price.
import { shippingMethodSchedule } from "@evolve-framework/medusa";
function shippingMethodSchedule(
schedule: DeliverySchedule,
): DeliveryOptionsProvider
OptionTypeDescription
methodsRecord<string, DeliveryScheduleEntry>

Schedule per shipping method key; a method without one offers no slots.

timeZone?string

The zone the windows and the order deadline are read in. Defaults to Europe/Amsterdam.

Returns DeliveryOptionsProvider

toCommerceErrorTranslate a failure from Medusa into the platform's error contract.
import { toCommerceError } from "@evolve-framework/medusa";

Anything Medusa reports as a client error keeps its message: those are validation failures a shopper needs to read ("this discount code has expired"). Anything else — a 5xx, a transport failure, a response shape we cannot map — collapses to a single generic message, because those messages carry internal detail and are no use to a shopper anyway. The original is logged by the caller, which has the request context to log it usefully.

function toCommerceError(error: unknown): GraphQLError

Returns GraphQLError

Constants

demoDeliveryScheduleA schedule for demo shipping methods, the way a Dutch shop offers its days.
import { demoDeliverySchedule } from "@evolve-framework/medusa";
const demoDeliverySchedule: DeliverySchedule
demoPickupPointsPickup points around Utrecht Centraal, for demo shipping methods per carrier.
import { demoPickupPoints } from "@evolve-framework/medusa";
const demoPickupPoints: PickupPointListOptions
ErrorCodeError codes this adapter emits.
import { ErrorCode } from "@evolve-framework/medusa";
const ErrorCode: { readonly INVALID_INPUT: "INVALID_INPUT"; readonly NOT_FOUND: "NOT_FOUND"; readonly UNAUTHENTICATED: "UNAUTHENTICATED"; readonly FORBIDDEN: "FORBIDDEN"; readon…
MEDUSA_TOKEN_NAMESPACENamespace for the Medusa-issued session inside the platform's federated token.
import { MEDUSA_TOKEN_NAMESPACE } from "@evolve-framework/medusa";
const MEDUSA_TOKEN_NAMESPACE: "medusa"

Types

ClientContextInfoWho a session belongs to, in the shape commercetools' /api/client-context answers, so a service reading it does not care which engine is behind it.
import { type ClientContextInfo } from "@evolve-framework/medusa";
export type ClientContextInfo = {
id: string;
givenName: string;
familyName: string;
email: string;
businessContext?: {
businessUnitId: string;
businessUnitKey: string;
};
};
ClientContextLoaderWhere a client context learns who its session belongs to.
import { type ClientContextLoader } from "@evolve-framework/medusa";
export interface ClientContextLoader {
getClientInfo(clientContext: ClientContext): Promise<ClientContextInfo | undefined>;
}
ClientFactoryOptions
import { type ClientFactoryOptions } from "@evolve-framework/medusa";
export type ClientFactoryOptions = {
baseUrl: string;
stores: MedusaStoreConfigs;
timeout?: number;
};
ContextValueThe per-request context every resolver in this package receives.
import { type ContextValue } from "@evolve-framework/medusa";

storeContext already carries a client scoped to the request's store and locale, so resolvers should read through it rather than building their own — a client built by hand is a client without the locale header.

type ContextValue$1 = ContextValue & {
log: typeof logger;
loaders: DataLoaders;
storeContext: StoreContext;
clientContext: ClientContext$1;
federatedToken?: FederatedToken;
globalScopedClient: () => RequestBuilder;
config: Record<string, unknown>;
request: RequestWithIP;
};
DeliveryOptionsProviderThe source of a cart's delivery dates and slots.
import { type DeliveryOptionsProvider } from "@evolve-framework/medusa";
export type DeliveryOptionsProvider = {
deliveryDates(request: DeliveryRequest, range: {
from?: string;
days: number;
}): Promise<DeliveryDate[]>;
deliveryPromise?(request: DeliveryPromiseRequest): Promise<DeliveryPromise>;
shippingMethodDelivery?(request: ShippingMethodDeliveryRequest): Promise<ShippingMethodDelivery>;
findSlot(request: DeliveryRequest, slotId: string): Promise<DeliverySlotChoice | undefined>;
};
DeliveryPromiseRequestWhat a product page asks a delivery provider: one variant, to one place.
import { type DeliveryPromiseRequest } from "@evolve-framework/medusa";
export type DeliveryPromiseRequest = {
client: RequestBuilder;
storeKey: string;
currency: string;
country: string;
postalCode?: string;
now: Date;
};
DeliveryRequestWhat a delivery provider is asked about: one cart, read in one store.
import { type DeliveryRequest } from "@evolve-framework/medusa";
export type DeliveryRequest = {
cart: MedusaCart;
client: RequestBuilder;
storeKey: string;
locale: string;
now: Date;
};
DeliveryScheduleWhen each shipping method delivers, keyed by shipping method key.
import { type DeliverySchedule } from "@evolve-framework/medusa";
export type DeliverySchedule = {
timeZone?: string;
methods: Record<string, DeliveryScheduleEntry>;
};
DeliveryScheduleEntryWhen the carrier behind one shipping method delivers.
import { type DeliveryScheduleEntry } from "@evolve-framework/medusa";
export type DeliveryScheduleEntry = {
carrier: {
key: string;
name: string;
};
windows: Array<{
from: string;
until: string;
}>;
weekdays: number[];
leadDays?: number;
orderBefore?: string;
countries?: Record<string, Partial<Omit<DeliveryScheduleEntry, "countries">>>;
};
DeliverySlotChoiceA slot a cart can be given, with the shipping option that charges for it.
import { type DeliverySlotChoice } from "@evolve-framework/medusa";
export type DeliverySlotChoice = {
slot: DeliverySlot;
shippingOptionId: string;
};
EntitySlug
import { type EntitySlug } from "@evolve-framework/medusa";
export type EntitySlug = {
reference: SlugReference;
reference_id: string;
locale_code: string;
value: string;
};
ErrorCodeValue
import { type ErrorCodeValue } from "@evolve-framework/medusa";
export type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
MedusaSession
import { type MedusaSession } from "@evolve-framework/medusa";
export type MedusaSession = {
accessToken: string;
customerId: string;
expiresAt: number;
refreshToken?: string;
};
MedusaStoreConfigPer-store Medusa settings.
import { type MedusaStoreConfig } from "@evolve-framework/medusa";

A publishable API key is what selects the sales channels a store sells from, so it is the store's identity as far as the Store API is concerned.

Regions and locales are deliberately absent. Region ids are generated per environment by the seed, and the locales a store serves are recorded on the store itself, so both would be a second copy of something Medusa already knows — and a .env that has to be edited whenever a shop opens a market or adds a language. They are read from Medusa at runtime and cached instead.

export type MedusaStoreConfig = {
publishableKey: string;
};
MedusaStoreConfigs
import { type MedusaStoreConfigs } from "@evolve-framework/medusa";
export type MedusaStoreConfigs = Record<string, MedusaStoreConfig>;
PickupPointChoiceA pickup point a cart can be given, with the shipping option that charges for it.
import { type PickupPointChoice } from "@evolve-framework/medusa";
export type PickupPointChoice = {
pickupPoint: PickupPoint;
shippingOptionId: string;
};
PickupPointEntryA pickup point as a list describes it.
import { type PickupPointEntry } from "@evolve-framework/medusa";
export type PickupPointEntry = {
id: string;
name: string;
carrier: {
key: string;
name: string;
};
type: "SHOP" | "LOCKER";
address: {
streetName: string;
streetNumber: string;
postalCode: string;
city: string;
country: string;
};
coordinates: {
latitude: number;
longitude: number;
};
openingHours: "always" | Partial<Record<number, Window[]>>;
unavailable?: DeliveryUnavailable;
};
PickupPointProviderThe source of a cart's pickup points.
import { type PickupPointProvider } from "@evolve-framework/medusa";
export type PickupPointProvider = {
pickupPoints(request: DeliveryRequest, query: PickupPointQuery): Promise<PickupPoint[]>;
findPickupPoint(request: DeliveryRequest, pickupPointId: string): Promise<PickupPointChoice | undefined>;
};
PickupPointQueryWhere and for which carriers a cart's pickup points are searched.
import { type PickupPointQuery } from "@evolve-framework/medusa";
export type PickupPointQuery = {
country: string;
postalCode?: string;
coordinates?: {
latitude: number;
longitude: number;
};
carrierKeys?: string[];
limit: number;
};
RequestBuilderA client scoped to a store and locale, without a signed-in customer.
import { type RequestBuilder } from "@evolve-framework/medusa";
export type RequestBuilder = Medusa;
ShippingMethodDeliveryWho delivers with a shipping option and when, each absent when unknown.
import { type ShippingMethodDelivery } from "@evolve-framework/medusa";
export type ShippingMethodDelivery = {
carrier?: Carrier;
estimate?: DeliveryEstimate;
};
ShippingMethodDeliveryRequestWhat the delivery step asks a delivery provider about one shipping option.
import { type ShippingMethodDeliveryRequest } from "@evolve-framework/medusa";
export type ShippingMethodDeliveryRequest = {
shippingOption: {
id: string;
key?: string;
};
country?: string;
now: Date;
};
UserRequestBuilderA client scoped to a store, locale and signed-in customer.
import { type UserRequestBuilder } from "@evolve-framework/medusa";
export type UserRequestBuilder = Medusa;

@evolve-framework/medusa/dataloaders

The request-scoped dataloaders the modules read products, categories and slugs through.

Functions

createCategoryByIdLoaderBatch category reads within a request.
import { createCategoryByIdLoader } from "@evolve-framework/medusa/dataloaders";
function createCategoryByIdLoader(
storeContext: StoreContext,
): DataLoader<string, MedusaCategory | undefined, string>

Returns DataLoader<string, MedusaCategory | undefined, string>

createDataLoadersBuild the per-request loader set.
import { createDataLoaders } from "@evolve-framework/medusa/dataloaders";

Every loader takes the store context rather than a bare client, which is what keeps a cached entity from crossing a store, locale or currency boundary: the loaders live and die with the request that made them. Given the client context, catalogue reads price for a signed-in customer, so their customer group's price lists apply.

function createDataLoaders(
storeContext: StoreContext,
clientContext?: ClientContext$1,
): DataLoaders

Returns DataLoaders

createProductByIdLoaderBatch product reads within a request.
import { createProductByIdLoader } from "@evolve-framework/medusa/dataloaders";

The loader is created per request and captures the store context, so its cache is scoped to one store, locale and currency. A process-wide loader keyed only by id would serve one locale's copy to another.

function createProductByIdLoader(
storeContext: StoreContext,
clientContext?: ClientContext$1,
): DataLoader<string, MedusaProduct | undefined, string>

Returns DataLoader<string, MedusaProduct | undefined, string>

createSlugsByIdLoaderAll locales' slugs per resource, batched.
import { createSlugsByIdLoader } from "@evolve-framework/medusa/dataloaders";

A product listing needs the slug of every row, and a detail page needs every locale for its alternate links. Both come from the same batched read rather than one request per row.

function createSlugsByIdLoader(
storeContext: StoreContext,
reference: SlugReference,
): DataLoader<string, EntitySlug[], string>

Returns DataLoader<string, EntitySlug[], string>

Types

DataLoaders
import { type DataLoaders } from "@evolve-framework/medusa/dataloaders";
export type DataLoaders = {
brandByKey: ReturnType<typeof createBrandByKeyLoader>;
categoryByHandle: ReturnType<typeof createCategoryByHandleLoader>;
categoryById: ReturnType<typeof createCategoryByIdLoader>;
categoryChildrenById: ReturnType<typeof createCategoryChildrenByIdLoader>;
productById: ReturnType<typeof createProductByIdLoader>;
productSlugsById: ReturnType<typeof createSlugsByIdLoader>;
categorySlugsById: ReturnType<typeof createSlugsByIdLoader>;
variantById: ReturnType<typeof createVariantByIdLoader>;
variantBySku: ReturnType<typeof createVariantBySkuLoader>;
};

@evolve-framework/medusa/modules

The modules: auth, business units, cart, catalog, checkout, customers, orders, order approvals, payments, quotes and shopping lists, and the route that turns Medusa's catalog events into internal events.

Classes

AuthModuleRegistration, sign-in and password recovery, served from Medusa's auth module.
import { AuthModule } from "@evolve-framework/medusa/modules";

Medusa keeps identity and profile apart: auth owns the credential and issues the token, customer owns the person. Registration therefore touches both, which is the one place this module reaches into the customer domain.

Extends AbstractModule<unknown>

new AuthModule(options?: AuthModuleOptions)
OptionTypeDescription
notifications?UserNotificationClient

Where the account-created and password-reset-complete notifications go, typically a QueuedUserNotificationClient from @evolve-framework/user-notifications. Without one none are published. The reset email itself is Medusa's to send.

clientFactory?ClientFactory

With it the module also serves POST /api/create-session, a guest session for a caller that needs one before its first GraphQL request, and POST /api/client-context, which RemoteClientContextLoader asks.

BusinessUnitModuleBusiness units, served by @evolve-framework/medusa-plugin-company.
import { BusinessUnitModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new BusinessUnitModule(options?: BusinessUnitModuleOptions)
OptionTypeDescription
notifications?UserNotificationClient

Where the associate-invited notification goes, a client from @evolve-framework/user-notifications. Without one none is published.

CartModuleThe basket.
import { CartModule } from "@evolve-framework/medusa/modules";

Medusa has no "active cart" of its own — a cart is reachable only by id — so the shopper's cart id rides on the session, which exists for guests too. That is what makes cart answerable without the storefront holding the id.

Extends AbstractModule<unknown>

new CartModule(options?: CartModuleOptions)
OptionTypeDescription
giftCards?boolean

Offer paying with a gift card. The backend needs the gift card plugin, since Medusa itself has no gift cards.

CatalogModuleThe catalog, served from Medusa: products, categories, category pages and productSearch, answering the shared schema unchanged.
import { CatalogModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new CatalogModule(options?: CatalogModuleOptions)
OptionTypeDescription
search?ProductSearchProvider

Where product search is answered from. Omitted, Medusa's product listing answers it — which needs no search infrastructure but returns no facet counts, because Medusa's product querying does not compute them.

More

An engine plugs in here by wrapping its query adapter. Medusa's own Search module is one of them, and needs the index @evolve-framework/medusa-plugin-search declares:

new CatalogModule({
	search: new ProductSearchProvider({
		adapter: new MedusaSearchQueryAdapter({ clientFactory }),
	}),
});

MeilisearchQueryAdapter plugs in the same way.

specifications?SpecificationOptions

How this catalogue's product attributes should be presented.

More

Every field here is a decision about one shop's merchandising, so it is passed in by the application rather than defaulted by the framework: which attribute is the brand, which specifications lead, and which are worth filtering on differ per project and per season.

Read-side on purpose. Making any of it import-side would put a full catalogue re-import between an editor and a reordered specification table.

Omitted, a catalogue exposes its specifications unhighlighted rather than guessing — which is the honest answer when nobody has said.

There is deliberately no facet list here. Which facets a listing offers is derived from what the results actually contain, not enumerated: see the query adapter's wildcard request.

stock?StockOptions

How a variant's stock is worded to a buyer.

dimensionTypes?DimensionTypes

Which product options a picker shows as colour swatches or images, by option title, such as { color: "color" }. Every other option is text.

listingFacets?ListingFacetOptions

Without search, which specifications a listing offers as facets besides the product options, and the labels attribute facets show.

CheckoutModuleTurning a basket, or an accepted quote, into an order.
import { CheckoutModule } from "@evolve-framework/medusa/modules";

Thin by design: Medusa's own complete route is transactional, so this module calls it, with no custom workflow of its own. Medusa completes only an authorised cart, so paymentCreate comes first here.

Extends AbstractModule<unknown>

new CheckoutModule()
CustomerModuleThe customer's own account: profile and address book.
import { CustomerModule } from "@evolve-framework/medusa/modules";

Every operation here acts as the signed-in customer, so reading or changing someone else's account is impossible by construction — Medusa resolves which customer from the token, and the token is the shopper's own. There is no customer id to pass and therefore none to tamper with.

Extends AbstractModule<unknown>

new CustomerModule()
DeliveryOptionsModuleDelivery dates, time slots and pickup points on Medusa: listing them on the cart and choosing one, which sets the shipping option that charges for it, and reading the choice back on the order.
import { DeliveryOptionsModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new DeliveryOptionsModule(options: DeliveryOptionsModuleOptions)
OptionTypeDescription
providerDeliveryOptionsProvider

Where the delivery dates and slots come from: shippingMethodSchedule(schedule) for the shop's own shipping options, keyed by their type code, or a carrier's API behind the same type.

pickupPoints?PickupPointProvider

Where pickup points come from: pickupPointList(points), or a carrier's location API behind the same type. Without it the cart offers none.

EventsModuleThe route @evolve-framework/medusa-plugin-events posts to, publishing the shared product and category events for what it forwards, so a CMS and a search index follow a Medusa catalog as they follow commercetools.
import { EventsModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new EventsModule(options: EventsModuleOptions)
OptionTypeDescription
clientFactoryClientFactory

The client a forwarded product or category is read back through. Its store keys are the store_keys every product event carries.

messagePublisherMessagePublisher

Where the internal events go.

originstring

Stamped on every event as its origin.

secret?string

The secret the plugin sends as x-events-secret. Without one any caller is accepted, which only suits a route nothing outside the cluster reaches.

path?string

Where the plugin posts. Defaults to "/events/medusa".

GiftCardModuleQuery.giftCardBalance, from the backend's gift card plugin: what is left on a card, from its number and, where it has one, its PIN.
import { GiftCardModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new GiftCardModule()
InvoiceModuleA company's and a customer's invoices, served by @evolve-framework/medusa-plugin-invoice.
import { InvoiceModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new InvoiceModule()
MultipleCartModuleSeveral named carts at once, shared across a company.
import { MultipleCartModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new MultipleCartModule(options?: MultipleCartModuleOptions)
OptionTypeDescription
archiveCartsAfterDays?number

How many days a cart may go untouched before it is archived. Defaults to 60.

OrderApprovalModuleOrder approvals, served by @evolve-framework/medusa-plugin-approval.
import { OrderApprovalModule } from "@evolve-framework/medusa/modules";

The two models meet awkwardly and it is worth being plain about where. The contract's OrderApproval.order is non-null: it assumes an order exists while approval is pending, which is how commercetools works. Medusa gates the cart and creates the order only once every signature is in — the better side of the difference, because completing a cart authorises payment, and holding money before anyone approved is what a spending control exists to prevent.

So a pending approval has no order to point at, and order reports that rather than inventing one. What does map cleanly is the configuration: the company's two switches become the contract's approval rules, and deciding an approval works whenever the caller has its id.

Composing this requires BusinessUnitModule: every approval field hangs off BusinessUnit.

Extends AbstractModule<unknown>

new OrderApprovalModule()
OrderModuleOrder history.
import { OrderModule } from "@evolve-framework/medusa/modules";

Everything here reads as the signed-in customer, so Medusa scopes results to their own orders. An order belonging to someone else is reported as not found rather than forbidden — confirming that an order exists is itself something worth not disclosing. Pass orderLinkSecret for orderBySignedLink, for an order page reached from an email without signing in.

Extends AbstractModule<unknown>

new OrderModule(options?: OrderModuleOptions)
OptionTypeDescription
orderLinkSecret?string

The secret order links are signed with, the same one passed to createOrderLinkToken from @evolve-framework/core/order-link where the link is made. Without it orderBySignedLink answers null.

PaymentModulePayment, served from Medusa's payment module: paymentCreate.
import { PaymentModule } from "@evolve-framework/medusa/modules";

Extends AbstractModule<unknown>

new PaymentModule()
QuoteModuleQuotes, served by @evolve-framework/medusa-plugin-quote.
import { QuoteModule } from "@evolve-framework/medusa/modules";

The plugin does more than this module exposes — a merchant can amend, send, reject and comment, and a customer can accept. The contract has only asking and reading, so that is what is mapped; the rest of the negotiation happens where the merchant already works, in the Medusa admin.

Composing this requires BusinessUnitModule: every quote field hangs off BusinessUnit, which that module defines.

Extends AbstractModule<unknown>

new QuoteModule()
ShoppingListModuleShopping lists, served by @evolve-framework/medusa-plugin-wishlist.
import { ShoppingListModule } from "@evolve-framework/medusa/modules";

Stock Medusa has no wishlists; the plugin adds the model and the routes, and this module is the adapter's side of them. Every read and write goes out as the signed-in customer, so a list is reachable only by whoever owns it — there is no call here that takes a customer id.

The module is also what the customer domain needs in order to compose at all: customer declares ProductVariant fields as @external for shopping-list items to require, and a subgraph carrying unused externals fails federation validation. Composing customer without shopping lists is not a smaller deployment, it is a broken one.

Without the plugin installed, every operation fails at the HTTP call rather than at start-up. That is the trade for keeping the plugin optional: the alternative — probing for it when the module is built — would put a request in the boot path of a service that may never serve a shopping list.

Extends AbstractModule<unknown>

new ShoppingListModule()

The product source a search index is built from, and a search provider for the catalog.

Classes

MedusaProductDocumentSourceProducts as search documents, one scope per store.
import { MedusaProductDocumentSource } from "@evolve-framework/medusa/search";

A scope carries every locale a store serves, and Medusa serves one locale per request — the translation module reads the x-medusa-locale header. So a page of products is fetched once per locale and merged into one document holding every language, which is the shape the shared document wants.

That is L requests per page rather than one. It is the price of translations living behind a per-request header, and it is paid by a background job rather than by a shopper.

new MedusaProductDocumentSource(options: MedusaProductDocumentSourceOptions)
OptionTypeDescription
clientFactoryClientFactory
storesMedusaStoreConfigs
perPage?number

Products per request. Medusa's store API caps a page at 100.

brandAttribute?string

Which stored attribute holds the brand, if the catalogue has one. Named rather than guessed — see the field on the document.

MethodDescription
getScopes(): Promise<IndexScope[]>Every scope this source feeds.
getDocuments( scope: IndexScope, sourceId: string, ): Promise<ProductDocument[]>Build every document for one source entity in one scope.
iterate(scope: IndexScope): AsyncIterable<ProductDocument[]>Every document for a scope, batched per source entity.
MedusaSearchQueryAdapterProduct search answered by Medusa's own Search module, through POST /store/search, against the index @evolve-framework/medusa-plugin-search declares.
import { MedusaSearchQueryAdapter } from "@evolve-framework/medusa/search";
new MedusaSearchQueryAdapter(options: MedusaSearchQueryAdapterOptions)
OptionTypeDescription
clientFactory?ClientFactory

Resolves a Store API client per store and locale.

matching?MedusaSearchMatching

How a typed term is matched. Left out, the last term is a prefix and a term that finds nothing is asked again with typos forgiven.

facetLabels?FacetLabels

Labels for attribute facets, by attribute name without attr_.

categoryFacet?boolean

Count products per category, under commercetools' categories key. Off until the index declares category_ids facetable; Medusa refuses to count a field it was not told to.

maxResults?number

The most hits the Search module's provider can page through. Medusa sets no limit itself; a provider backed by a search engine has that engine's.

client?Medusa

One Store API client, for a caller that already holds the right one.

MethodDescription
init(): Promise<void>Nothing to connect: every search is one request to Medusa.
search( query: ProductSearchQuery, ): Promise<SearchResponse<ProductDocument>>Run a query and return the documents that matched.
ProductSearchProvider
import { ProductSearchProvider } from "@evolve-framework/medusa/search";
new ProductSearchProvider(options: ProductSearchProviderOptions)
OptionTypeDescription
adapterSearchQueryAdapter<ProductDocument>

Types

MedusaSearchMatchingHow a typed term is matched against the index.
import { type MedusaSearchMatching } from "@evolve-framework/medusa/search";
export type MedusaSearchMatching = {
prefix?: boolean;
typoFallback?: boolean | {
fields: (locale: string) => string[];
};
};

@evolve-framework/medusa/sitemap

The product source for sitemap generation.

Classes

MedusaProductSitemapSourceProducts as sitemap entries, one scope per store and locale.
import { MedusaProductSitemapSource } from "@evolve-framework/medusa/sitemap";

The slug plugin already holds every locale's slug for a product, so a page of products needs exactly one extra request to become a page of localised URLs — rather than one request per locale, which is what reading translated handles out of Medusa would cost.

supportsIdRange is false. Medusa's store API pages by offset and has no cursor on id, so a partitioned rebuild cannot hand each worker a disjoint slice; the generator pages the collection itself instead.

new MedusaProductSitemapSource(options: MedusaProductSitemapSourceOptions)
OptionTypeDescription
clientFactoryClientFactory
storesMedusaStoreConfigs
perPage?number

Products per request. Medusa's store API caps a page at 100.

MethodDescription
getScopes(): Promise<SitemapScope[]>One scope per locale each store serves, as Medusa reports them.
iterate( scope: SitemapScope, _range?: IdRange, ): AsyncIterable<SitemapEntry[]>Every entry for one scope, batched, so a source pages its backend once per batch rather than once per entry.

Not listed: 10 options types. They appear as the tables under the constructors and functions they type.