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)| Option | Type | Description |
|---|---|---|
getClientInfo | (clientContext: ClientContext$1) => Promise<ClientContextInfo | undefined> |
| Method | Description |
|---|---|
clientInfo(): Promise<ClientContextInfo | undefined> | Who the session belongs to, read once per request; undefined for a guest. |
isAuthenticated(): boolean | |
setAccessToken(serialised: string): void | Adopt a session arriving on the request. |
load(): Promise<void> | Settle the session for this request. |
setSession(session: MedusaSession): void | Record a freshly issued Medusa session on the federated token. |
clearSession(): void | |
setActiveCart(cartId: string | undefined): void | |
ensureWishlistAnonymousId(): string | The anonymous wishlist owner, minting one if the shopper has none. |
servesStore(storeKey: string): boolean | Whether this service serves a store, so a business context may name it. |
setActiveBusinessUnit(
businessUnitId: string | undefined,
storeKey?: string,
): void | |
startGuestSession(): boolean | Open a guest session, unless a session of any kind is already there. |
recordGuestOrder(orderId: string): void | |
clearGuestOrders(): void | |
clearWishlistAnonymousId(): void | |
clientFor(accessToken: string): Medusa | A client acting as the bearer of a specific token. |
requireUserClient(): Medusa | A 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()| Method | Description |
|---|---|
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()| Method | Description |
|---|---|
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)| Method | Description |
|---|---|
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)| Option | Type | Description |
|---|---|---|
storeKey | string | |
locale | string | |
currency | string |
| Method | Description |
|---|---|
taxCountry(): string | undefined | The 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(): voidReturns void
commerceError
import { commerceError } from "@evolve-framework/medusa";function commerceError( message: string, code: ErrorCodeValue, extensions?: Record<string, unknown>,): GraphQLErrorReturns 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| Option | Type | Description |
|---|---|---|
MEDUSA_API_URL | string | The Medusa server's URL, the base of its Store API. |
MEDUSA_STORES | MedusaStoreConfigs | The stores this service serves, as JSON keyed by store key, each with its
|
MEDUSA_REQUEST_TIMEOUT | number | Milliseconds a request to Medusa may take, reading the response included,
before it is aborted with a |
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| Option | Type | Description |
|---|---|---|
points | PickupPointEntry[] | |
shippingMethods | Record<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| Option | Type | Description |
|---|---|---|
methods | Record<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): GraphQLErrorReturns GraphQLError
Constants
demoDeliveryScheduleA schedule for demo shipping methods, the way a Dutch shop offers its days.
import { demoDeliverySchedule } from "@evolve-framework/medusa";const demoDeliverySchedule: DeliveryScheduledemoPickupPointsPickup points around Utrecht Centraal, for demo shipping methods per carrier.
import { demoPickupPoints } from "@evolve-framework/medusa";const demoPickupPoints: PickupPointListOptionsErrorCodeError 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,): DataLoadersReturns 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)| Option | Type | Description |
|---|---|---|
notifications? | UserNotificationClient | Where the account-created and password-reset-complete notifications go,
typically a |
clientFactory? | ClientFactory | With it the module also serves |
BusinessUnitModuleBusiness units, served by @evolve-framework/medusa-plugin-company.
import { BusinessUnitModule } from "@evolve-framework/medusa/modules";Extends AbstractModule<unknown>
new BusinessUnitModule(options?: BusinessUnitModuleOptions)| Option | Type | Description |
|---|---|---|
notifications? | UserNotificationClient | Where the associate-invited notification goes, a client from
|
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)| Option | Type | Description |
|---|---|---|
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)| Option | Type | Description |
|---|---|---|
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. MoreAn engine plugs in here by wrapping its query adapter. Medusa's own Search
module is one of them, and needs the index
|
specifications? | SpecificationOptions | How this catalogue's product attributes should be presented. MoreEvery 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 |
listingFacets? | ListingFacetOptions | Without |
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)| Option | Type | Description |
|---|---|---|
provider | DeliveryOptionsProvider | Where the delivery dates and slots come from:
|
pickupPoints? | PickupPointProvider | Where pickup points come from: |
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)| Option | Type | Description |
|---|---|---|
clientFactory | ClientFactory | The client a forwarded product or category is read back through. Its store
keys are the |
messagePublisher | MessagePublisher | Where the internal events go. |
origin | string | Stamped on every event as its |
secret? | string | The secret the plugin sends as |
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)| Option | Type | Description |
|---|---|---|
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)| Option | Type | Description |
|---|---|---|
orderLinkSecret? | string | The secret order links are signed with, the same one passed to
|
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()@evolve-framework/medusa/search
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)| Option | Type | Description |
|---|---|---|
clientFactory | ClientFactory | |
stores | MedusaStoreConfigs | |
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. |
| Method | Description |
|---|---|
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)| Option | Type | Description |
|---|---|---|
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 |
categoryFacet? | boolean | Count products per category, under commercetools' |
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. |
| Method | Description |
|---|---|
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)| Option | Type | Description |
|---|---|---|
adapter | SearchQueryAdapter<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)| Option | Type | Description |
|---|---|---|
clientFactory | ClientFactory | |
stores | MedusaStoreConfigs | |
perPage? | number | Products per request. Medusa's store API caps a page at 100. |
| Method | Description |
|---|---|
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.

