Releases: medusajs/medusa
Release list
v2.19.0: Vite v7 Update, Inventory Export, Custom Fulfillment Addresses
Highlights
Medusa MCP users can update their project using the following prompt:
Update my Medusa project to v2.19.0Update Vite and React Router to v7
🚧 Breaking change
This release updates the Medusa Admin dashboard to use Vite v7.3.6 and React Router v7.18.2. This is a breaking change and requires you to make the following changes:
- Update your
vitedirect dependency tov7.3.6 - Update your
react-router-domandreact-routerdirect dependencies to v7.18.2 - Use Node ^20.19.0, ^22.12.0, or greater LTS. Node 20.0–20.18 and 22.0–22.11 are no longer supported.
Due to this update, there are the following breaking changes related to Vite and React Router that may impact you if you use these features:
- Supported browsers are now changed to Chrome >= 107, Edge >= 107, Firefox >= 104, Safari >= 16 (previously 87 / 88 / 78 / 14)
- The Vite config allowed in
medusa-config.ts'sadmin.viteconfiguration now supports Vite 7 config, which makes the following changes:build.target: "modules"is removed. the Vite 7 default is "baseline-widely-available".splitVendorChunkPluginis removed. Usebuild.rollupOptions.output.manualChunksinsteadresolve.conditionsis changed from adding your values to the default, to replacing them.
Response.json()is removed due to the supported browser change. Return or throw aResponseinstead. For example,new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json; charset=utf-8" } }).defer()is removed from the React Router API. Loaders return a plain object now; unresolved promises are streamed automatically, so<Await>keeps working unchanged.UIMatch.datafrom React Router is deprecated in favour ofUIMatch.loaderData, which holds the same value, andUIMatch.datais now typed as possibly undefined. This includes theprops.dataform used by detail-route breadcrumb components, which becomesprops.loaderData.
JS SDK Admin Product Option Methods Removed
🚧 Breaking change
The following methods have been removed from the JS SDK as they targeted endpoints that no longer exist:
sdk.admin.product.createOptionsdk.admin.product.updateOptionsdk.admin.product.retrieveOptionsdk.admin.product.deleteOption
To manage a product's options, use sdk.admin.product.update instead.
Changed Default Behavior for Totals Fetching in Carts and Orders
Previously, when retrieving * fields of carts and orders through Query or API routes, that didn't include totals. You had to request those specifically.
Retrieving all fields with * now includes the computed total fields. So, for better performance, make sure to request only the fields you actually need for carts and orders rather than using *.
Support JSONB Types in .json DML Properties
Previously, a property in a data model typed with .json had the TypeScript type of an object, even though the property stores any JSON-serializable value in a jsonb column.
You can now specify the actual type of the column in a type argument of the json method. This only affects the generated type of the property.
For example:
import { model } from "@medusajs/framework/utils"
const Brand = model.define("brand", {
id: model.id().primaryKey(),
name: model.text(),
warnings: model.json<{ code: string; message: string }[]>(),
})Inventory Item Export
Inventory items can now be exported from the admin dashboard as a CSV file. The export workflow is available through the admin UI and via the API, matching the pattern already established for products and orders.
Custom Delivery Address and Additional Data for Fulfillment Creation
When creating a fulfillment, you can now supply a custom delivery address independently of the order's shipping address, and pass arbitrary additional_data through to fulfillment providers. This enables use cases such as drop-shipping to a third-party address or forwarding provider-specific metadata without customizing core flows.
Notification Preferences for Order Edits
Order edit workflows now respect notification preferences. When triggering an order edit, you can opt in or out of sending notifications to the customer, consistent with the preference controls already available on other order operations.
Features
- feat(auth-oidc,dashboard,auth,js-sdk,types,medusa): generic OIDC auth provider by @NicolasGorga in #16023
- feat: Implement search module by @sradevski in #16298
- feat: Replace admin search with a BE endpoint by @sradevski in #16358
- feat: Implement a DSL for search index by @sradevski in #16391
- feat(core-flows,medusa,dashboard,js-sdk,types): add inventory item export by @srindom in #16223
- feat(fulfillment, core-flows, types, utils, medusa): support custom delivery address + pass additional data to createFulfillment by @shahednasser in #16139
- feat(core-flows,dashboard,js-sdk, medusa,types,utils): support notification preferences for order edits by @shahednasser in #16238
- feat(eslint-plugin): prefer workflow events by @leobenzol in #16150
Bugs
- fix(test-utils, modules-sdk, utils, medusa): fix plugin:add for monorepo projects by @shahednasser in #16308
- fix: Improve typings for schema metadata by @sradevski in #16400
- fix(core-flows): delete auth identity when possible upon customer/user deletion by @NicolasGorga in #16176
- fix(caching,core-flows,framework,query,types,utils): pass non automatically computed tags to various cached queries by @NicolasGorga in #16354
- fix(pricing): allow updating prices with an empty rules object by @lazerg in #16267
- fix(medusa): document the invite accept token as a query parameter by @lazerg in #16323
- fix(utils): support array and dynamic types for
.jsonfields in DML by @shahednasser in #16236 - fix(cart): fetch totals with all fields + fix inconsistent total calculation by @shahednasser in #16022
- fix(core-flows): guard calculated price set before reading it in cart line-item prep by @zain-asif-dev in #15939
- fix(order): fetch totals with all fields + fix inconsistent total calculation by @shahednasser in #16021
- fix(medusa): allow admin draft orders without an email or customer_id by @GBreg19 in #16133
- fix(dashboard): hide Property Labels settings item unless view_configurations is enabled by @shahednasser in #16255
- fix(core-flows): honor item-level allow_backorder when confirming inventory by @shafi-VM in #15731
- fix(admin-shared): add missing gift_card.list.side injection zone by @shahednasser in #16218
- fix(core-flows): re-price merged cart line items and hydrate compare_at on update by @shahednasser in #16211
- fix(index): preserve date filters by @DS123-ally in #16186
- fix(dashboard,draft-order): update @hookform/resolvers and react-hook-form to show validation errors in admin forms by @shahednasser in #16208
- fix(dashboard): fetch currency_code for visible currency columns in configurable tables by @Nahid-NHB in #16195
- fix(core-flows): pass the cart's currency and region to fulfillment providers when calculating shipping option prices by @shahednasser in [#16...
v2.18.0
Highlights
This release comes with new features, bug fixes, and dependency updates for better security.
Medusa MCP users can update their project using the following prompt:
Update my Medusa project to v2.18.0Balanced Query Load Strategy by Default
🚧 Breaking change
The default database load strategy has changed from SELECT_IN to BALANCED, matching MikroORM v7's intended default. MikroORM picks between a joined and a select-in approach per relation, which improves query performance in most cases. Because it changes how relations are loaded, review query-heavy paths and any snapshot or query-count assertions in your tests after upgrading.
Return Value of Generated Internal Service's delete Method Changed
🚧 Breaking change
This affects usages of generated internal services. They do not impact methods generated by
MedusaServicesuch asdeletePosts.
The return value of the delete method of a generated internal service has been changed to cater for composite primary keys:
// before
delete(idOrSelector: string, sharedContext?: Context): Promise<string[]>
delete(idOrSelector: string[], sharedContext?: Context): Promise<string[]>
delete(idOrSelector: object, sharedContext?: Context): Promise<string[]>
delete(idOrSelector: object[], sharedContext?: Context): Promise<string[]>
delete(
idOrSelector: {
selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
},
sharedContext?: Context
): Promise<string[]>
// after
delete(
idOrSelector: string,
sharedContext?: Context
): Promise<string[] | Record<string, any>[]>
delete(
idOrSelector: string[],
sharedContext?: Context
): Promise<string[] | Record<string, any>[]>
delete(
idOrSelector: object,
sharedContext?: Context
): Promise<string[] | Record<string, any>[]>
delete(
idOrSelector: object[],
sharedContext?: Context
): Promise<string[] | Record<string, any>[]>
delete(
idOrSelector: {
selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
},
sharedContext?: Context
): Promise<string[] | Record<string, any>[]>The method now either returns an array of deleted IDs if the primary key is an id field, or an array of objects, each object's keys are the composite primary key's name, and value is the primary key's value. For example, [{ foo: "foo-1", var: "bar-1" }].
If you use generated internal services in your module, make sure to update their usages to handle the new return value. For example:
// before
const deletedIds = await postService.delete(idsOrObject) // type was string
// after
// type could be an array of string IDs or objects.
const deletedData = await postService.delete(idsOrObject)
const isStringIds = deletedData.some((data) => typeof data === "string")Improved Configurable Data Tables in the Admin Dashboard
The view configurations feature in the Medusa Admin dashboard. has been improved Users can resize columns, control column visibility and ordering, and save view configurations, with dynamic filter and sort resolution, custom cell renderer registration, and a UI for managing property labels.
Learn more in this documentation
view-config-2.mp4
Transfer an Order to a Guest Customer
Orders can now be transferred to a guest customer from the admin dashboard without going through a full order transfer flow. This makes it possible to reassign an order to a guest account when it was placed under the wrong customer or needs to be detached from a registered account. The original customer transfer flow remains for transferring orders to registered customers.
New Inventory Events
The inventory workflows now emit domain events, so you can subscribe to inventory changes and run side effects such as syncing stock to external systems or notifying on low levels.
Short-Lived Database Credentials for AWS RDS IAM
createPgConnection now forwards dynamicPassword and expirationChecker from databaseDriverOptions to the underlying connection. When running on AWS RDS with IAM authentication (or any setup with short-lived credentials such as Azure AD tokens, GCP IAM, or Vault dynamic secrets), tokens expire after roughly 15 minutes. Previously these fields were silently dropped, causing authentication failures once the pool opened new connections. You can now mint a fresh token per connection:
// medusa-config.ts
import { Signer } from "@aws-sdk/rds-signer"
import { defineConfig } from "@medusajs/framework/utils"
const signer = new Signer({ hostname, port, username })
export default defineConfig({
projectConfig: {
databaseDriverOptions: {
dynamicPassword: () => signer.getAuthToken(),
expirationChecker: () => true, // always fetch a fresh token
},
},
})Stripe Payment Method Configurations
The @medusajs/payment-stripe provider now supports Stripe Payment Method Configurations (PMC) through a new paymentMethodConfiguration option. The value is forwarded as payment_method_configuration when creating a PaymentIntent, and can be overridden per request via extra.payment_method_configuration. This lets merchants manage which payment methods appear to customers from the Stripe Dashboard, without code changes or redeploys.
// medusa-config.ts
{
resolve: "@medusajs/payment-stripe",
id: "stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
paymentMethodConfiguration: "pmc_xxx",
},
}Security: Dependency Updates
This release resolves all Dependabot security alerts by bumping vulnerable dependencies. Notable updates include:
multer(2.0.2 to 2.2.0)qs(6.14.0 to 6.15.2)@opentelemetry/sdk-node(0.218.0 to 0.220.0)@opentelemetry/resourcesand@opentelemetry/sdk-trace-node(2.0.0 to 2.7.0)
If you've explicitely installed any of these packages, make sure to update to the same new version to avoid unexpected issues. For the full list of updated dependencies check out PRs #16083, #16089, #16082, and 16158
Features
- feat: Implement support for cross-module filtering at the DAL by @sradevski in #15918
- feat: Pass crossjoinable info to joiner by @sradevski in #15951
- feat(admin-vite-plugin,dashboard): add config.label to document title resolution seo fallback by @NicolasGorga in #15984
- feat(dashboard,medusa,types): filter notifications in admin dashboard for logged in user by @NicolasGorga in #15923
- feat(core-flows): automatically refresh taxes upon state change by @NicolasGorga in #15924
- feat(dashboard): resizable datagrid columns by @NicolasGorga in #15925
- feat(core-flows, utils): emit inventory-related events by @shahednasser in #15991
- feat(eslint-plugin): add a rule for wildcard + specific field selections in query by @shahednasser in #16037
- feat(core-flows,medusa,types,js-sdk,dashboard): transfer order to guest customer by @NicolasGorga in #15926
- feat(dashboard,admin-vite-plugin,admin-shared,ui,settings,js-sdk,types): view configuration UI enhancements — dynamic filter/sort resolution, custom cell renderer registration, property labels management UI by @NicolasGorga in #14661
- feat(framework): add helpful hint for admin users using incorrect api key header by @shahednasser in #16062
- feat: Implement support for cross-module joins by @sradevski in #15979
- feat: Use a balanced load strategy which will be the default in mikro… by @sradevski in #16137
- feat(payment-stripe): add paymentMethodConfiguration option by @vssavosko in #15191
- feat(utils): add Iranian Toman (IRT) currency by @KMLnk in #15948
- feat(dashboard): pass order ID as a query parameter in storefront payment link by @shahednasser in #16127
- feat: Implement second stage of cross-module joins by @sradevski i...
v2.17.2
Highlights
before / after on widgets injection zones
🚧 Breaking change
Since the components in a LayoutComposer controlled page are now arranged through the Editor view (including widgets), the .after | .before widget injection zones don't have an effect on where the Widget is placed within its injection zone - you should configure this, just like with any other layout component, through the Editor view. The .side is still relevant for Two Column page layouts.
New Admin Layout Composer
The admin dashboard layout is now fully customizable through a reworked Layout Composer. You can rearrange the Topbar, Sidebar, Settings Sidebar and pages with drag and drop, and your arrangement is persisted in the database so it stays consistent across sessions and users.
This rework started as a community contribution from @leobenzol, who built the drag-and-drop Layout Composer and settings persistence and applied it across the shell. Thanks to their work, extending and reordering the dashboard layout is now a first-class part of the admin. A prime example of the kind of high-value features the community can drive in Medusa. Read more in the announcement.
Async Payment Methods Support
Medusa now supports asynchronous payment methods, where a payment is confirmed after the initial request through provider webhooks rather than synchronously. The support spans the Payment module, the Stripe provider, core workflows, the JS SDK, and the admin dashboard, so async methods are handled consistently across the checkout and payment lifecycle.
Tiered Pricing for Price List Prices
Price list prices now support quantity-based tiers directly in the admin. Click a price cell in the price list edit form to define multiple price tiers per variant and currency, making it straightforward to set up bulk discounts and other quantity-based pricing strategies across regions and currencies.
CleanShot.2026-07-01.at.13.57.02.mp4
Contextual Browser Tab Titles
The admin dashboard now sets a descriptive document title for each page, so multiple open tabs are easy to tell apart. List pages show the section name (for example "Products - Medusa") and detail pages surface the entity name (for example a product's title or an order's display id). Titles are resolved through a new seo resolver on each route's handle.
Custom admin pages in your own project can set their tab title the same way, by exporting a handle with an seo resolver from the route file. Return a static title, or derive one from the route's loader data via match.data:
// src/admin/routes/custom/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk"
export const handle = {
seo: () => ({ title: "My Custom Page" }),
}
export const config = defineRouteConfig({
label: "My Custom Page",
})
const CustomPage = () => {
return <div>My custom page</div>
}
export default CustomPageFeatures
- feat: drag&drop LayoutComposer, settings db persistence by @leobenzol in #15721
- feat(payment,payment-stripe,core-flows,medusa,dashboard,js-sdk,utils,types): introduce async payment methods support by @NicolasGorga in #15085
- feat(dashboard): add dynamic document titles for browser tabs by @bqst in #14426
- feat(admin-shared,dashboard): apply new LayoutComposer approach across the admin dashboard by @NicolasGorga in #15861
- feat(admin-shared,dashboard): use layout composer in Topbar, Sidebar and settings Sidebar by @leobenzol in #15862
- feat(dashboard): add quantity-based pricing support for price lists by @laaibaQasim in #15258
Bugs
- fix(payment-stripe): handle async payment method check gracefully in webhook by @NicolasGorga in #15897
- fix(core-flows): keep customer account-holder link on a failed payment-session init by @Mezzle in #15618
- fix(utils): allow alias override in defineLink InputOptions to support linking the same module twice by @kowalski21 in #15701
- fix(order): make custom_display_id searchable by @Dev-Abdullah-H in #15622
- fix: Product option and product option values are not translatable by @weknowyourgame in #15881
- fix(core-flows,medusa): exclude gift cards from tax calc and add an option for custom gift card codes by @scherddel in #15419
- fix: move the remote joiner class to the modules SDK package by @sradevski in #15890
- fix(order): use isDefined check for unit_price in item-update action by @Dev-Abdullah-H in #15863
Chores
- chore: add package bugs metadata to published packages by @Floofy6 in #15683
- chore: remove unused test suites by @sradevski in #15888
- chore: remove legacy API tests that are no longer relevant by @sradevski in #15877
New Contributors
- @Mezzle made their first contribution in #15618
- @kowalski21 made their first contribution in #15701
- @laaibaQasim made their first contribution in #15258
- @weknowyourgame made their first contribution in #15881
- @Floofy6 made their first contribution in #15683
Full Changelog: v2.17.1...v2.17.2
v2.17.1
Highlights
Regression with workers in Redis Event Bus
This release fixes a critical bug introduced in 2.17.0.
What
Not await bullWorker_.run() in event-bus-redis onApplicationStart
Why
bullWorker_.run() is designed to return only when the worker is closed (taskforcesh/bullmq#2128). This bug had flown under the radar until we started awaiting all modules onApplicationStart here. The effect is that when running in worker mode, application startup hangs indefinitely. It does not happen in server mode because bullWorker_ only exists in worker mode.
Bugs
- fix: fix event-bus-redis onApplicationStart hook by @peterlgh7 in #15838
- fix: properly handle undefined bullWorker by @peterlgh7 in #15842
Other Changes
- i18n(ja): complete dashboard translations — fill 511 missing keys by @greymoth-jp in #15839
New Contributors
- @greymoth-jp made their first contribution in #15839
Full Changelog: v2.17.0...v2.17.1
v2.17.0
Highlights
IMPORTANT: This release contains a regression around worker instances. You should not upgrade to this but instead 2.17.1.
Global Product Options
🚧 Breaking change
Product options in Medusa can now be global — defined once at the store level and reusable across any number of products. Previously, options such as "Size" or "Color" had to be recreated independently for each product. With this release, you define an option once, attach it to as many products as you need, and manage values from a single place. This unlocks consistent variant modeling across large catalogs and reduces duplication when building storefront filters or admin tooling.
Read more in the announcement post.
Provider-Agnostic Auth Verification
🚧 Breaking change
Auth verification (email, phone, etc.) has been reworked into a flexible, provider-based system. You can now declare exactly which verifications are required per actor type and auth provider via the new authVerificationsPerActor config — for example, require email verification for customers using emailpass, but skip it for those signing in with Google. Codes are issued and confirmed through pluggable code providers, with a built-in token provider out of the box.
This is a breaking change:
- The verification endpoints moved from
POST /auth/:actor_type/:auth_provider/verification/request(and/confirm) to flatPOST /auth/verification/requestandPOST /auth/verification/confirm. The request route is now authenticated and takesentity_id,entity_type, andcode_providerin the body instead of actor/provider in the URL. - The JS SDK's auth verification methods were updated to match — upgrade the SDK and adjust any custom verification calls.
- A database migration replaces the
auth_verification_tokentable with a newauth_verificationtable. Runnpx medusa db:migrateafter upgrading; any pending (unconfirmed) verifications are discarded.
Medusa ESLint Plugin
A new @medusajs/eslint-plugin package ships with this release, bringing first-party linting rules for Medusa projects. The plugin covers API routes, subscribers, scheduled jobs, admin customizations, and module patterns. Lint runs automatically via the Medusa CLI when the plugin is installed:
npx medusa lintRules are grouped into config presets (recommended, modules, etc.) so you can opt in to the level of strictness that fits your project.
#15719
#15697
#15700
#15714
#15715
#15717
Features
- feat: global product options by @willbouch in #13817
- feat(order,types): add line_item_metadata to order responses by @NicolasGorga in #15727
- feat(dashboard): allow already registered actor to accept admin invite by @NicolasGorga in #15791
- feat(file-s3): add acl option to disable ACL headers on uploads by @mrpackethead in #15764
- feat: Revamp auth verification setup by @sradevski in #15696
- feat(admin-shared,dashboard,draft-order,loyalty): LayoutComposer, injection zones for plugins by @leobenzol in #15478
- feat(admin): add internal note support to order edits by @Tusharkhadde in #15690
- feat(cli, eslint-plugin, medusa): add linting to medusa CLI by @shahednasser in #15719
- feat(eslint-plugin): add rules for API routes by @shahednasser in #15697
- feat(eslint-plugin): added admin customization rules by @shahednasser in #15700
- feat(eslint-plugin): added rules for subscribers by @shahednasser in #15714
- feat(eslint-plugin): added rules for scheduled jobs by @shahednasser in #15715
- feat(eslint-plugin): add remaining eslint rules by @shahednasser in #15717
- feat(eslint-plugin): add
modulesconfig preset and support ESLint 8.57+ by @shahednasser - feat: pass scheduledFor to job handler by @peterlgh7 in #15815
- feat: Add publish timestamp to event metadata by @peterlgh7 in #15814
Bugs
- fix: handle bodyparser errors by @peterlgh7 in #15749
- fix: Run linting by default if eslint plugin is installed by @sradevski in #15816
- fix(medusa): update variant mutation endpoints query config to its retrieve query config by @NicolasGorga in #15735
- fix(core-flows): include order shipping method names in tax context by @gaoflow in #15783
- fix(dashboard): don't call hooks after an early return in UserLink by @merkelis-p in #15751
- fix(dashboard): don't call useTranslation inside NavItem items.map() by @merkelis-p in #15750
- fix(caching): invalidate list caches on entity update events by @imharjot in #15747
- fix: log single error log line by @peterlgh7 in #15748
- fix(medusa): maintain ESLint config detection behavior by @shahednasser in #15776
- fix: Remove unused actor type in auth module by @sradevski in #15761
- fix(eslint-plugin): handle link edge cases by @shahednasser in #15774
- fix(eslint-plugin): fix and improve main config by @shahednasser in #15758
- fix(eslint-plugin): fixes to avoid false positives by @shahednasser in #15743
- fix(core-flows, loyalty-plugin, medusa): fix medusa lint errors by @shahednasser in #15732
- fix(framework,medusa): surface real error and terminate process on db commands by @NicolasGorga in #15726
- fix(loyalty-plugin): respect user locale in currency formatting by @adem-loghmari in #15725
- fix(dashboard): feature flag rbac sidebar entries by @NicolasGorga in #15733
- fix(dashboard): prevent URL param collision breaking pagination in price list add products modal by @Tusharkhadde in #15704
- fix(pricing): return override as original_amount when a sale is stacked on an override by @cainydev in #15541
- fix(framework): match build ignore list against path segments by @sapirbaruch in #15577
- fix(core-flows): scope calculated shipping provider items to the option's shipping profile by @langovoi in #15163
- fix(promotion): prevent negative taxable base when stacking promotions by @shafi-VM in #15532
- fix(utils,dashboard): add GMD (Gambian Dalasi) to default currency lists by @kzroo in #15266
- fix: Await onapplicationstart by @sradevski in #15786
Documentation
- docs: product options changes by @shahednasser in [#14290](https://github...
v2.16.0
Highlights
This release comes with many improvements and bug fixes. We highly recommend updating to leverage these changes in your application.
For the Medusa MCP users, you can ask your AI agent to update your project with the following prompt:
update my Medusa project to v2.16.0It will fetch the necessary changes needed to update your project.
Update Prompt for AI Agents
<role>
You are a Medusa upgrade specialist. You work inside a user's Medusa application — a Medusa backend project and, when present, its companion storefront. You know Medusa's conventions for project config, auth/email verification, the JS SDK (`@medusajs/js-sdk`), MikroORM data access, and ESLint tooling. You make no change the user has not approved.
</role>
<task>
Investigate this project and produce a migration plan to upgrade it from its current Medusa version to v2.16.0, then present the plan for the user's approval before making any edits.
</task>
<context>
v2.16.0 is a minor release with several breaking changes that require code or config updates. This prompt covers only the required upgrade steps and breaking changes — additive features in this release (tax line context hook, multi-shipping-method carts, new/custom admin injection zones) are intentionally out of scope; do not implement them.
The breaking changes in scope:
1. **Package version bump to v2.16.0** for all `@medusajs/*` packages.
2. **MikroORM bumped to 6.6.14** (security fix for CVE-2026-44680). `manager.find` now throws on relations that don't exist on an entity instead of silently ignoring them.
3. **`react-router-dom` bumped to `6.30.4`** (defensive security update). Admin customizations may break if not updated.
4. **ESLint plugin** (`@medusajs/eslint-plugin`). New projects ship with it; existing projects should add it. Once configured, `medusa build` and `medusa develop` run linting by default.
5. **Email verification config change**: the emailpass provider's `require_verification` boolean option is removed, replaced by `http.authVerificationsPerActor`.
6. **Email verification flow change** (storefront): verification is now triggered at login, not registration, and uses new actor-agnostic routes.
7. **Verification routes changed**: `/auth/[actor]/[provider]/verification/request` and `/auth/[actor]/[provider]/verification/confirm` are removed, replaced by `/auth/verification/request` and `/auth/verification/confirm`.
8. **JS SDK email-verification signature changes** for `auth.register`, `auth.login`, `auth.verification.request`, and `auth.verification.confirm`.
9. **Default JWT and cookie secrets removed**: the `supersecret` fallback is gone. In production, the app throws and fails to start if `http.jwtSecret` / `http.cookieSecret` are not set.
For anything not covered here, consult the official Medusa documentation at https://docs.medusajs.com or the Medusa MCP server before acting. Do not guess at APIs, config keys, or route shapes — verify them.
</context>
<inputs>
You are given access to the project's working directory. You must discover the following yourself; do not assume:
- **Project shape**: standalone Medusa project vs. monorepo (e.g. `apps/backend` + workspaces). Check for a root `package.json` with workspaces and an `apps/` directory.
- **Storefront presence**: a separate storefront app/repo or directory that uses `@medusajs/js-sdk`. If no storefront is in this workspace, treat storefront steps as guidance to surface to the user, not edits you can make.
- **Current Medusa version**: read from `package.json` dependencies.
- **Whether the project uses email verification**: search for `require_verification`, `authVerificationsPerActor`, `/auth/*/verification/`, `sdk.auth.verification`, or `verification_required`.
- **Whether secrets are configured**: inspect `medusa-config.ts`/`.js` for `http.jwtSecret` / `http.cookieSecret` and the environment for `JWT_SECRET` / `COOKIE_SECRET`.
- **Whether `react-router-dom` is a direct dependency.**
- **Whether custom code calls `manager.find` directly** (raw MikroORM access outside the module service abstractions).
</inputs>
<steps>
Work through these in order. For each, record findings and the proposed change in the plan — do not edit yet.
1. **Detect project shape and current version.** Read the relevant `package.json` files. Note standalone vs. monorepo and the storefront location (if any). Record the current `@medusajs/medusa` version.
2. **Plan the package version bump.** Identify every `@medusajs/*` dependency and devDependency across the backend (and admin/plugin packages if monorepo) and target `2.16.0`. Note that `@medusajs/ui` does not follow the `2.x` line — if it is a direct dependency anywhere (commonly in admin customizations), target `4.1.16` rather than `2.16.0`. If `react-router-dom` is a direct dependency anywhere (commonly in admin customizations or storefront), target `6.30.4`. Plan a single install/upgrade pass and note the package manager in use (yarn/npm/pnpm — detect from lockfile).
3. **Audit JWT and cookie secrets.** Check whether `http.jwtSecret` and `http.cookieSecret` are set in config or via `JWT_SECRET` / `COOKIE_SECRET` env vars. The default `supersecret` fallback is removed; in production a missing value throws at startup and the app fails to boot. If they are unset or still rely on the default, flag this as a **must-fix before deploying** item and propose setting them via environment variables. Never invent or hardcode secret values — instruct the user to generate strong secrets and set the env vars.
4. **Audit direct `manager.find` usage.** Search custom code for direct MikroORM `manager.find` calls that pass `fields`/`populate` referencing relations. Under MikroORM 6.6.14 these now throw if a referenced relation/property does not exist on the entity. For each occurrence, plan to validate field/populate paths against the entity metadata before the call (drop paths that don't map to a real property/relation), mirroring how Medusa prunes them internally. If no direct `manager.find` usage exists, record that this step is N/A.
5. **Plan ESLint plugin setup.** This is strongly recommended; once configured, `medusa build` and `medusa develop` lint by default and `medusa develop` fails to start on lint errors.
- Add dev dependencies: `@medusajs/eslint-plugin`, `eslint`, and `jiti`. Install at the monorepo root for monorepos, or directly in the project for standalone projects (and in plugins). `jiti` is required: the config is written in TypeScript (`eslint.config.ts`), and ESLint 9 uses `jiti` to load and transpile a TS config file at runtime. Without it, linting fails to load the config.
- Create the flat config (`eslint.config.ts`, or `.js`/`.mjs`):
- **Standalone project / plugin:**
```ts
import { defineConfig } from "eslint/config"
import medusa from "@medusajs/eslint-plugin"
export default defineConfig([...medusa.configs.recommended])
```
- **Monorepo root** (`eslint.config.ts`): same as above.
- **Monorepo backend** (\`apps/backend/eslint.config.ts\`): same as above.
- Add `"eslint.config.*"` to the `exclude` array in the backend `tsconfig.json` to avoid type errors on the config file.
- Add a `lint` script to the backend's `package.json` so the command is easy to run, e.g. `"lint": "medusa lint"`.
- Note the new `medusa lint` command (supports `--fix` and `--quiet`) and the `--no-lint` flag for `medusa build` / `medusa develop`. Recommend running `medusa lint --fix` after upgrade and surfacing remaining lint errors to the user.
6. **Plan email verification config migration (backend).** Only if the project uses email verification.
- Remove the `require_verification` option from the emailpass provider configuration.
- Add `http.authVerificationsPerActor` under `projectConfig` in `medusa-config.*`. Its type is `Record<actorType, { entity_type: string; auth_provider: string }[]>`. An empty array for an actor type means no verification required. Example:
```ts
http: {
authVerificationsPerActor: {
user: [],
customer: [
{ entity_type: "email", auth_provider: "emailpass" },
],
},
}
```
- Preserve the project's existing intent: map the previous `require_verification: true/false` (and any per-actor expectations) onto the new per-actor structure. Confirm with the user which actor types require verification if it is ambiguous.
7. **Plan the `auth.verification_requested` subscriber migration (backend).** Only if the project has a subscriber handling the `auth.verification_requested` event (search for `auth.verification_requested` or a `verificationRequestedHandler`). The event payload changed in v2.16.0:
- `token` is renamed to **`code`** — use `code` to build the verification link.
- `provider` is renamed to **`code_provider`** (defaults to `"token"`).
- `actor_type` is **removed**. Replace the `actor_type !== "customer"` guard with an `entity_type` check, e.g. `if (entity_type !== "email") return`.
- `provider_identity_id` is **removed**.
- `entity_type` (e.g. `"email"`) and an optional `metadata?: Record<string, unknown>` are **added**. `entity_id` is still the email/identifier.
Migration example:
```ts
// Before
export default async function verificationRequestedHandler({
event: { data: { entity_id: email, token, actor_type } },
container,
}: SubscriberArgs<{
entity_id: string
token: string
actor_type: string
provider: string
auth_identity_id: string
provider_identity_id: string
expires_at: string
}>) {
if (actor_type !== "customer") {
return
}
// ...verification_url uses `token`
}
// After
export default async function verificationRequested...
v2.15.5
Highlights
Multi-Factor Authentication
Medusa now supports multi-factor authentication (MFA). The admin dashboard includes a complete MFA UI that allows users to set up and manage their authentication methods. MFA lifecycle events are now emitted for tracking authentication flows.
After updating, make sure to set the AUTH_MFA_ENCRYPTION_KEY environment variable to a random 64-character string:
AUTH_MFA_ENCRYPTION_KEY=your_random_64_character_stringAlso, if you've added the Auth Module to your medusa-config.ts file to set any of its options, make sure to set the mfa.encryption_key option to the same environment variable:
import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/auth",
dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
options: {
mfa: {
encryption_key: process.env.AUTH_MFA_ENCRYPTION_KEY,
},
// other options...
},
},
],
})If you don't set the mfa.encryption_key option, you'll get a "MFA encryption key is required to use MFA methods" error whenever trying to enroll or verify an MFA factor.
Features
- feat: add admin MFA UI by @christiananese in #15493
- Emit MFA lifecycle events by @christiananese in #15495
- Emailpass email verification primitives by @christiananese in #15496
- feat(dashboard,framework,rbac,js-sdk,types,utils,medusa): rbac admin dashboard utils by @fPolic in #14593
Bugs
- fix(core-flows): avoid refunding captures made in separate completeCartWorkflow executions by @NicolasGorga in #15527
- fix(utils): add mfa to inline snapshot test assertion by @NicolasGorga in #15518
- fix(core-flows): respect allow_backorder when calculating pickup inventory availability by @marlinjai in #15440
- Allow cancelling pending MFA setup by @christiananese in #15475
- fix(dashboard): order list status badges show correct colors when view_configurations is enabled by @shiminshen in #15430
- fix(core-flows): use hasPermission util to perform checks in validateUserRolePermissionsStep by @NicolasGorga in #15470
- fix(core-flows,medusa): align validate user permissions check with hasPermission util by @NicolasGorga in #15465
Documentation
- docs: update cloudflare config by @shahednasser in #15499
- docs: migrate main docs to cloudflare by @shahednasser in #15498
- docs: add TSDocs for "rbac admin dashboard utils (#14593)" by @shahednasser in #15476
- doc: migrate to cloudflare + medusa cloud by @shahednasser in #15446
- docs: fix with ai in cloud by @shahednasser in #15474
Chores
- chore: add tests for stock location metadata in response by @jasonmerx in #15448
- chore: fix indexing job for algolia by @shahednasser in #15504
- chore: fix release pipeline by @shahednasser in #15500
- chore: fix sync action checkout step by @shahednasser in #15481
- chore: add commit hash option to sync actions by @shahednasser in #15480
- chore: fix sync actions by @shahednasser in #15479
- chore(docs): automated cloud documentation update by @shahednasser in #15473
- chore(docs): fix common issues in the docs-generator by @shahednasser in #15464
- chore(docs): Updated API Reference (automated) by @github-actions in #15461
- chore(docs): Generated References (automated) by @github-actions in #15462
- chore(docs): Generated DML JSON files (automated) by @github-actions in #15458
- chore(docs): Updated UI Reference (automated) by @github-actions in #15460
- chore(docs): Update version in documentation (automated) by @github-actions in #15459
- chore(docs): doc changes for next release (automated) by @shahednasser in #15380
- chore: fix trigger release job conflict by @shahednasser in #15457
- Chore: Release by @github-actions in #15477
- Chore: Release by @github-actions in #15467
Full Changelog: v2.15.3...v2.15.5
v2.15.3
Highlights
Multi-Factor Authentication Support
This release adds the primitives to support Multi-Factor Authentication (MFA) for enhanced security. This includes new authentication provider primitives, API routes for MFA management, and retrieval functionality. The implementation provides a foundation for integrating various MFA methods.
Promotion Code Visibility Improvements
When promotion codes are skipped due to budget or usage limits, the system now surfaces this information to provide better visibility into why certain promotions weren't applied. This helps merchants understand promotion application behavior and troubleshoot issues.
Features
- feat(js-sdk): add MFA auth helpers by @christiananese in #15441
- feat(ui): add CodeInput component by @christiananese in #15424
Bugs
- fix(design-system): broaden React peer dependencies to support v18 an… by @Suh0161 in #15271
- fix(core-flows): harden create payment sessions when customer has no account holders by @Suh0161 in #15264
- fix(dashboard): include inventory query in detail key by @Derekko-web in #15417
- fix(dashboard): complete and correct Thai (th) translations by @Ligament in #15409
- fix(test-utils, link-modules): encode URL credentials and fix schema-qualified RENAME TO by @Ultron03 in #15344
- fix(medusa): fix filtering by categories and tags in /store/products with the index module by @shahednasser in #15405
- fix(create-medusa-app): fix incorrect command replacement when using yarn and npm by @shahednasser in #15436
- fix(utils): implement tokenized free text search by @Suh0161 in #15275
- fix(core-flows): fix incorrect stock location picked for item with backorder in a sales channel with multiple locations by @shahednasser in #15159
Documentation
- docs: configure posthog capturing by @shahednasser in #15449
- docs: fix information about preview environments by @shahednasser in #15445
- docs: fix documentation issues in triage inbox by @shahednasser in #15427
- docs: revert Cloudflare migration by @shahednasser in #15438
- docs: add logging by @shahednasser in #15435
- docs: prepare to deploy to medusa cloud by @shahednasser in #15429
- docs: track logged in users by @shahednasser in #15425
- docs: added cloud docs for backups by @shahednasser in #15408
- docs: migrate to cloudflare by @shahednasser in #15388
- docs: fix mcp instructions for cursor by @shahednasser in #15401
- docs: add TSDocs for "add MFA provider primitives by @shahednasser in #15387
Chores
- chore(docs): cloud doc changes (automated) by @shahednasser in #15451
- chore: fix docs automation job by @shahednasser in #15452
- chore: fix required secrets in review and triage actions by @shahednasser in #15421
- chore: fix actions required anthropic api key by @shahednasser in #15410
- chore: switch actions to use anthropic api key by @shahednasser in #15404
- chore(docs): Updated UI Reference (automated) by @app/github-actions in #15391
- chore(docs): Generated References (automated) by @app/github-actions in #15393
- chore(docs): Update version in documentation (automated) by @app/github-actions in #15390
- chore: fixes to pr reviewer and issue triager by @shahednasser in #15394
Full Changelog: v2.15.2...v2.15.3
v2.15.2
Highlights
Fix Migrations Regressions
Following the MikroORM update in v2.13.6, a regression was introduced where running medusa db:migrate generated a full database snapshot for custom modules. Then, when running medusa db:generate later, a bug in the migration generation caused the module snapshot to be overridden by the database snapshot, which would then generate migrations dropping all the tables in the database.
We encourage all users on a version after v2.13.6 to update to this release to mitigate database issues.
After updating, please inspect the migrations/.snapshot.**.json files in your custom modules and:
- Delete any snapshots with the name
.snapshot-<db-name>.json. - For module snapshots (
.snapshot-<module-name>.json), confirm that they don't include table names outside of your module, such asproduct,order, etc... if so, delete those snapshots and regenerate again withmedusa db:generate.
You can also use the following prompt with your AI agent:
AI Agent Prompt
# Cleaning Up Incorrect Snapshot Files
A bug in earlier versions of Medusa caused `db:migrate` to generate snapshot files named after the database (e.g. `.snapshot-medusa.json`) instead of the module. In some cases, running `db:generate` afterward would overwrite the correct module snapshot with this full-database snapshot, causing the next generated migration to contain `DROP TABLE` statements for all Medusa core tables.
Follow the steps below after updating to v2.15.2 that includes this fix.
## Step 1: Delete database-name snapshots
Look in each module's `migrations/` directory for snapshot files named after your database rather than the module. These are incorrectly generated files and should be deleted.
They look like:
```
.snapshot-medusa.json
.snapshot-my-store.json
.snapshot-medusa-medusa-resolutions.json
```
The correct snapshot is named after the module:
```
.snapshot-<module-name>.json
```
Delete any file that does not follow the module-name pattern.
## Step 2: Check for corrupted module snapshots
Open the remaining `.snapshot-<module>.json` file for each module and check its `tables` array. It should only contain tables owned by that module.
If it contains tables from other modules (e.g. `order`, `cart`, `product`, `customer` inside a small custom module), the snapshot was overwritten by the full-database snapshot and is corrupted.
## Step 3: Regenerate corrupted snapshots
For each corrupted snapshot:
1. Delete the corrupted snapshot file.
2. Regenerate it by running:
```bash
npx medusa db:generate <moduleName>
```
## Step 4: Review the regenerated migration
Open the newly created migration file and check its contents. If it contains `DROP TABLE` or unexpected `ALTER TABLE` statements for tables that belong to other modules:
1. **Do not run this migration.**
2. Delete the migration file.
3. The snapshot has now been regenerated correctly — subsequent runs of `db:generate` will produce accurate migrations.Features
- feat(medusa): enable index when querying products via promotion attribute values and enable sku search in products entrypoint by @NicolasGorga in #15386
Bugs
- fix(utils): disable generating snapshots on running migrations by @shahednasser in #15382
- fix: build should throw if medusa-config throws by @peterlgh7 in #15383
- fix(locking-redis): add jitter to Redis lock retry backoff by @Suh0161 in #15274
Chores
- chore(docs): Generated References (automated) by @app/github-actions in #15376
- chore(docs): Updated UI Reference (automated) by @app/github-actions in #15374
- chore(docs): Update version in documentation (automated) by @app/github-actions in #15373
Full Changelog: v2.15.1...v2.15.2
v2.15.1
Highlights
Includes missing PRs from 2.15.0 release
Due to an issue during the release process of 2.15.0, several PRs listed in the release notes weren't included. If you've already installed 2.15.0, you might have experienced behavior matching this description. If you want the changes listed in the release notes of said version, you should install this one instead. The issue in the release process has already been corrected.
Chores
- chore(docs): Generated + Updated UI Reference (automated) by @app/github-actions in #15368
- chore(docs): Update version in documentation (automated) by @app/github-actions in #15367
- chore(docs): doc changes for next release (automated) by @shahednasser in #15257
Full Changelog: v2.15.0...v2.15.1