medusa

Frameworks & Libraries

medusa release notes.

Latest v2.20.1 · by medusaWritten in TypeScriptWebsitemedusajs/medusaRSS

Release activity

Release activity — 13 releases across 12 days since May 11, 2026. Each cell is one day; darker means more releases that day. Nothing is recorded before May 11, 2026. Older weeks are hidden at this screen width.
JunJulAugSep
SundayNo releases on May 24, 2026No releases on May 31, 2026No releases on Jun 7, 2026No releases on Jun 14, 2026No releases on Jun 21, 2026No releases on Jun 28, 2026No releases on Jul 5, 2026No releases on Jul 12, 2026No releases on Jul 19, 2026No releases on Jul 26, 2026No releases on Aug 2, 2026No releases on Aug 9, 2026No releases on Aug 16, 2026No releases on Aug 23, 2026No releases on Aug 30, 2026No releases on Sep 6, 2026
MondayNo releases on May 25, 20261 release on Jun 1, 2026No releases on Jun 8, 2026No releases on Jun 15, 2026No releases on Jun 22, 2026No releases on Jun 29, 2026No releases on Jul 6, 2026No releases on Jul 13, 2026No releases on Jul 20, 2026No releases on Jul 27, 2026No releases on Aug 3, 2026No releases on Aug 10, 2026No releases on Aug 17, 2026No releases on Aug 24, 2026No releases on Aug 31, 2026No releases on Sep 7, 2026
TuesdayNo releases on May 26, 2026No releases on Jun 2, 2026No releases on Jun 9, 2026No releases on Jun 16, 2026No releases on Jun 23, 2026No releases on Jun 30, 2026No releases on Jul 7, 2026No releases on Jul 14, 2026No releases on Jul 21, 2026No releases on Jul 28, 2026No releases on Aug 4, 2026No releases on Aug 11, 2026No releases on Aug 18, 2026No releases on Aug 25, 2026No releases on Sep 1, 2026No releases on Sep 8, 2026
WednesdayNo releases on May 27, 2026No releases on Jun 3, 2026No releases on Jun 10, 2026No releases on Jun 17, 20261 release on Jun 24, 20261 release on Jul 1, 2026No releases on Jul 8, 2026No releases on Jul 15, 2026No releases on Jul 22, 2026No releases on Jul 29, 2026No releases on Aug 5, 2026No releases on Aug 12, 2026No releases on Aug 19, 2026No releases on Aug 26, 20261 release on Sep 2, 2026
ThursdayNo releases on May 28, 2026No releases on Jun 4, 2026No releases on Jun 11, 20261 release on Jun 18, 20261 release on Jun 25, 2026No releases on Jul 2, 2026No releases on Jul 9, 2026No releases on Jul 16, 20261 release on Jul 23, 2026No releases on Jul 30, 2026No releases on Aug 6, 20261 release on Aug 13, 2026No releases on Aug 20, 2026No releases on Aug 27, 20261 release on Sep 3, 2026
FridayNo releases on May 29, 2026No releases on Jun 5, 2026No releases on Jun 12, 2026No releases on Jun 19, 2026No releases on Jun 26, 2026No releases on Jul 3, 2026No releases on Jul 10, 2026No releases on Jul 17, 2026No releases on Jul 24, 2026No releases on Jul 31, 2026No releases on Aug 7, 2026No releases on Aug 14, 2026No releases on Aug 21, 2026No releases on Aug 28, 2026No releases on Sep 4, 2026
SaturdayNo releases on May 30, 2026No releases on Jun 6, 2026No releases on Jun 13, 2026No releases on Jun 20, 2026No releases on Jun 27, 2026No releases on Jul 4, 2026No releases on Jul 11, 2026No releases on Jul 18, 2026No releases on Jul 25, 2026No releases on Aug 1, 2026No releases on Aug 8, 2026No releases on Aug 15, 2026No releases on Aug 22, 2026No releases on Aug 29, 2026No releases on Sep 5, 2026

13 releases since May 11, 2026, busiest day 2

Changelog

v2.20.1Latest

v2.20.1: Field Filter Stripping Fix, Search Stability, Schema Index Validation

Fixed 2
  • Remove correlated flag on search until it is supported
  • Disallow using JSON for schema index specification
Security 1
  • Always strip fields that a field filter does not allow, removing the RBAC flag requirement

From medusa

Highlights
Field Filter Always Strips Disallowed Fields

Previously, the allowed query config on a route and http.restrictedFields config were not being applied unless the RBAC flag was enabled. This releases fixes it by removing the RBAC flag requirement. We highly recommend updating to this version as this is a valuable security fix.

Bug Fixes
Contributors

Thank you to all contributors who made this release possible:

View originalPermalink
How v2.20.1 went
v2.20.0

v2.20.0: Security Fixes, Performance Improvements, Calculated Shipping Options in Draft Orders

Added 6
  • Store API routes now enforce a default relations limit of three levels to prevent performance degradation from deeply nested expansions
  • Product routes allow four levels of relation expansions, one level deeper than other Store API routes
  • Configure the relations limit globally via storeRelationsLimit in HTTP configuration or per-route via validateAndTransformQuery middleware
  • Inventory items can now be tracked in fractional quantities with an associated unit of measure
  • Draft orders now support shipping options with calculated prices from third-party rate providers
  • New setCalculatedShippingPricingContext hook allows injecting additional data into the pricing context for fulfillment provider rate calculations
Changed 3
  • Redis caching provider now stores entries under a 64-bit hash key with a simplified storage mechanism, invalidating existing cache entries on upgrade
  • searchMany method has been moved to the search provider interface requiring custom search providers to implement it
  • Search indexes are no longer created at application startup and must be created by running db:migrate
Removed 1
  • In-memory local search provider @medusajs/search-local has been removed
Security 2
  • Payment providers are now validated against the cart's region to prevent customers from initiating payment sessions with providers not linked to their region
  • MFA challenge enforcement on protected routes prevents attackers with a valid password from regenerating recovery codes or modifying MFA factors without completing MFA

From medusa

Highlights

Medusa MCP users can update their project using the following prompt:

Update my Medusa project to v2.20.0
Payment providers are now validated against the cart's region

Security fix: creating a payment session no longer trusts the payment provider ID passed in the request. The createPaymentSessionsWorkflow and the cart payment validation step now verify that the chosen provider is actually enabled in the cart's (or payment collection's) region, and reject the request otherwise. Previously a store customer could initiate a payment session with any payment provider installed in the application, even one that was not linked to their region.

No action is required, but if your storefront relied on passing an arbitrary provider ID, make sure the provider is linked to the region the cart belongs to.


MFA Challenge Enforcement on MFA Routes

Security fix: An attacker who already had a user's valid password could use that token to regenerate the recovery codes through POST /auth/mfa/recovery-codes and use one of them to complete the challenge, or enroll and delete MFA factors. This releases fixes this issue by tracking whether a user has completed MFA before allowing them to access the recovery codes route.


Relations Limit on Store API Routes

🚧 Breaking change

This only affects Store API requests that expand deeply nested relations through fields. Admin routes are unaffected.

Store API routes now reject requests that expand more than three levels of relations in a single query by default, since deeply nested expansions can cause bad performance. A request such as fields=*products.variants.options.values now returns a 400 error listing the offending fields.

The product routes are the only core Store routes that raise the limit above the default, allowing four levels of relations:

  • GET /store/products
  • GET /store/products/:id

To change the limit for every Store route, set storeRelationsLimit in your HTTP configuration:

module.exports = defineConfig({
  projectConfig: {
    http: {
      storeRelationsLimit: 4,
    },
  },
})

To change it for a specific route, set storeRelationsLimit in the query configuration passed to validateAndTransformQuery in your middlewares. It takes precedence over the application-wide configuration:

import { defineMiddlewares, validateAndTransformQuery } from "@medusajs/framework/http"

export default defineMiddlewares({
  routes: [
    {
      matcher: "/store/custom",
      method: "GET",
      middlewares: [
        validateAndTransformQuery(GetCustomSchema, {
          defaults: ["id", "*items"],
          storeRelationsLimit: 5,
          isList: true,
        }),
      ],
    },
  ],
})

Redis Caching Storage Format

This only affects projects using the @medusajs/caching-redis provider.

The Redis caching provider now stores entries under a 64-bit hash key and uses a simplified storage mechanism. Existing cache entries written by earlier versions are not readable in the new format and are effectively invalidated on upgrade, so expect a cold cache after deploying. No configuration change is required.


Fractional Inventory Quantities and Units of Measure

Inventory items can now be tracked in fractional quantities with an associated unit of measure, unlocking use cases such as selling by weight, length, or volume. The support spans the Inventory Module, link modules, API routes, and workflows, and the admin dashboard surfaces the unit of measure across the inventory screens.


Calculated Shipping Options in Draft Orders

Draft orders now support shipping options with calculated prices, so third-party rate providers can be used when an operator builds an order manually. A new setCalculatedShippingPricingContext hook lets you inject additional data into the pricing context sent to the fulfillment provider when calculating those rates.


Breaking Changes
  • Relations limit on store routes — Store API routes now enforce a default relations limit to cap query depth and improve performance. Requests that relied on unbounded relation expansion may see fewer nested associations returned. Run db:migrate after upgrading. (#16688)

  • In-memory local search provider removed@medusajs/search-local (the Orama-backed in-memory provider shipped in v2.19) has been removed. Projects must switch to @medusajs/search-postgres, the Medusa Cloud provider, or a custom provider. Search indexes are no longer created at application startup; run db:migrate to create them. (#16545)

  • searchMany moved to the provider interface — The searchMany method has been pushed down to the search provider interface so each provider can optimize batch queries internally. Custom search providers must implement searchMany. (#16643)

  • MFA routes now require a completed challenge — MFA-protected routes now validate that the MFA challenge has actually been completed before granting access, hardening the authentication flow. (#16610)


Other Changes
Features
  • Vector-based search for Medusa Search — Added foundational vector search support including namespace management, vector ingestion, and improved search-definition APIs. (#16631, #16673, #16676)

  • ESLint plugin: new workflow rules — Three new rules for @medusajs/eslint-plugin: no-nested-when-then catches nested when/then blocks that silently do not compose correctly; missing-when-name requires all when calls to have a name argument for better traceability; throw-in-workflow-definition disallows throw statements in workflow definition functions, which must use MedusaError in steps instead. (#16520, #16585, #16517)

Bug Fixes
  • Payment provider validation on cart — Validate that the payment provider selected for a cart actually belongs to the cart's region before processing. (#16690)

  • Store order routes field expansion — Blocked field-expansion pivot paths on store order routes to prevent information leakage. (#16480)

  • Product category scalar fields — Return all scalar fields from product category queries when no select is specified. (#16493)

  • Variant fields for inventory quantity — Automatically select required variant fields when inventory quantity is requested; handle null variants gracefully. (#16444)

  • Order shipping method fields — Select shipping method fields correctly when listing orders with totals. (#16241)

  • Credit line totals — Preserve net negative credit line totals instead of zeroing them out. (#16303)

  • OAuth state cache — Await the OAuth state cache write so the state is persisted before the redirect completes. (#16571)

  • Redis caching — Fixed the Redis provider overwriting keys on set; await set and clear calls in the caching module; removed an unnecessary quadratic computation in the caching layer. (#16549, #16547, #16496)

  • created_by on fulfillment/shipment — The actor ID is now correctly recorded as created_by when creating fulfillments and shipments. (#16582)

  • 4xx span status — 4xx HTTP responses no longer set the OpenTelemetry span status to Error, reducing noise in tracing dashboards. (#16561)

  • Draft order item change deletion — Fixed inability to delete pending draft order item change actions from the dashboard. (#16616)

  • Create fulfillment form pagination — Fixed select pagination issues in the create-fulfillment form. (#16495)

  • Duplicate query in product — Removed a duplicate query call in the product module. (#16530)

  • Promotion campaign budget type — Campaign budget type is now correctly included in promotion status columns. (#16254)

  • Isolated request allowed fields — Request allowedFields are now isolated per-request so one request cannot bleed its field allowlist into another. (#16313)

  • Admin bundler zoom — The generated admin HTML no longer blocks browser zoom. (#16449)

  • HTML lang attribute — The admin dashboard now sets the correct lang attribute on the <html> element. (#16450)

  • Deleted stock locations in fulfillment — The order fulfillment section now handles deleted stock locations without crashing. (#16006)

  • CSV export columns — Fixed column output for order CSV exports. (#16216)

  • Workflow execution state column — The workflow execution state column in the dashboard now has its own translation key to avoid conflicts. (#16594)

  • Loyalty plugin — Fixed account locking on debit, gift card expiry date validation, and gift card product editing following global product-option changes. (#16347, #16348, #16475)

  • Test utilitiesprocess.env is now restored after test runner cleanup so environment mutations do not leak between test suites. (#16273)

Translations
  • Updated Croatian admin translations. (#16675)
  • Updated Russian (ru) admin translations: added missing keys, plural forms (_few/_many), and fixed mistranslations. (#16402)
  • Fixed missing Polish plural forms in dashboard translations. (#16638)

Contributors

Thank you to all the contributors who made this release possible:

View originalPermalink
How v2.20.0 went
v2.19.0

v2.19.0: Vite v7 Update, Inventory Export, Custom Fulfillment Addresses

Added 7
  • Support specifying actual type of JSONB columns using type argument in .json() method for data models
  • Add inventory item export functionality from admin dashboard as CSV file
  • Support custom delivery address when creating a fulfillment independently of the order's shipping address
  • Allow passing arbitrary additional_data through to fulfillment providers during fulfillment creation
  • Add notification preferences for order edits to respect customer notification preferences
  • Implement generic OIDC authentication provider
  • Implement search module with DSL for search index
Changed 6
  • Update Medusa Admin dashboard to use Vite v7.3.6 and React Router v7.18.2
  • Require Node ^20.19.0, ^22.12.0, or greater LTS, dropping support for Node 20.0–20.18 and 22.0–22.11
  • Update supported browsers to Chrome >= 107, Edge >= 107, Firefox >= 104, Safari >= 16
  • Change resolve.conditions in Vite config to replace default values instead of adding to them
  • Use sdk.admin.product.update instead to manage a product's options
  • Include computed total fields when retrieving * fields of carts and orders through Query or API routes
Removed 5
  • Remove build.target: "modules" from Vite config in favor of Vite 7 default "baseline-widely-available"
  • Remove splitVendorChunkPlugin from Vite config, use build.rollupOptions.output.manualChunks instead
  • Remove Response.json() API, return or throw a Response object instead
  • Remove defer() from React Router API, loaders now return plain objects with unresolved promises streamed automatically
  • Remove sdk.admin.product.createOption, sdk.admin.product.updateOption, sdk.admin.product.retrieveOption, and sdk.admin.product.deleteOption methods from JS SDK
Deprecated 1
  • Deprecate UIMatch.data from React Router in favor of UIMatch.loaderData

From medusa

Highlights

Medusa MCP users can update their project using the following prompt:

Update my Medusa project to v2.19.0
Update 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:

  1. Update your vite direct dependency to v7.3.6
  2. Update your react-router-dom and react-router direct dependencies to v7.18.2
  3. 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's admin.vite configuration now supports Vite 7 config, which makes the following changes:
    • build.target: "modules" is removed. the Vite 7 default is "baseline-widely-available".
    • splitVendorChunkPlugin is removed. Use build.rollupOptions.output.manualChunks instead
    • resolve.conditions is changed from adding your values to the default, to replacing them.
  • Response.json() is removed due to the supported browser change. Return or throw a Response instead. 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.data from React Router is deprecated in favour of UIMatch.loaderData, which holds the same value, and UIMatch.data is now typed as possibly undefined. This includes the props.data form used by detail-route breadcrumb components, which becomes props.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.createOption
  • sdk.admin.product.updateOption
  • sdk.admin.product.retrieveOption
  • sdk.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.

#16139


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.

#16238

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 .json fields 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 #16207
  • fix(dashboard): add tooltip for full product/variant name in pricing data grids by @andershermansen in #15962
  • fix(js-sdk): remove dead product-option methods from admin.product by @shahednasser in #16187
  • fix(dashboard): fix active sidebar link detection by @shahednasser in #16191
  • fix(file-s3): preserve directory portion of filename in object key by @Tusharkhadde in #16010
  • fix(core-flows): recompute draft order tax lines on address change and conversion by @shahednasser in #16033
  • fix(product): delete individual variant metadata fields by @ihopenre-eng in #16179
Documentation
Chores
New Contributors
  • @NicolasGorga made their first contribution in #16176
  • @sradevski made their first contribution in #16391
  • @lazerg made their first contribution in #16267
  • @zain-asif-dev made their first contribution in #15939
  • @GBreg19 made their first contribution in #16133
  • @shafi-VM made their first contribution in #15731
  • @DS123-ally made their first contribution in #16186
  • @Nahid-NHB made their first contribution in #16195
  • @andershermansen made their first contribution in #15962
  • @Tusharkhadde made their first contribution in #16010
  • @ihopenre-eng made their first contribution in #16179
  • @irontaek made their first contribution in #16233
  • @leobenzol made their first contribution in #16150

Full Changelog: v2.18.0...v2.19.0

View originalPermalink
How v2.19.0 went

v2.18.0

Added 5
  • View configurations feature in the Medusa Admin dashboard now supports resizing columns, controlling column visibility and ordering, and saving view configurations with dynamic filter and sort resolution
  • Orders can now be transferred to a guest customer from the admin dashboard without going through a full order transfer flow
  • Inventory workflows now emit domain events for subscribing to inventory changes and running side effects such as syncing stock to external systems
  • createPgConnection now forwards dynamicPassword and expirationChecker from databaseDriverOptions to support short-lived database credentials for AWS RDS IAM and other dynamic secret systems
  • @medusajs/payment-stripe provider now supports Stripe Payment Method Configurations through a new paymentMethodConfiguration option
Changed 2
  • Default database load strategy changed from SELECT_IN to BALANCED to match MikroORM v7's intended default, improving query performance by picking between joined and select-in approaches per relation
  • Return value of generated internal service's delete method now returns either an array of deleted IDs or an array of objects with composite primary key names and values

From medusa

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.0
Balanced 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 MedusaService such as deletePosts.

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

https://github.com/user-attachments/assets/995cdb60-f9d7-4f1b-9ce9-3b24671130fb


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.

#15926


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.

#15991


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
    },
  },
})

#15686


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",
  },
}

#15191


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/resources and @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 in #16131
  • feat(dashboard,settings,ui,types): implement configurable data tables end to end in admin dashboard by @NicolasGorga in #16025
  • feat(create-medusa-app): detect nub package manager by @colinhacks in #15898
  • feat: Extract query engine to package by @sradevski in #15900
  • feat(core-flows,types,utils,medusa): support global product options in imports by @shahednasser in #15614
  • feat(docs): JSON doc-model TypeDoc theme + generator by @shahednasser in #16042
  • feat(docs): serve the JSON doc-model + remove MDX by @shahednasser in #16044
  • feat: Cleanup query package structure by @sradevski in #15934
  • feat: Refactor joiner by @sradevski in #15972
  • feat(dashboard): update Brazilian Portuguese (ptBR) translations by @diwberg in #16015
  • feat(utils): support dynamic password function in createPgConnection for RDS IAM auth by @mrpackethead in #15686
  • feat(types,core-flows,order): add metadata to tax lines returned by providers by @nam-stx in #15840
Bugs
  • fix(dashboard): call useStateAwareTo unconditionally in RouteFocusModal by @merkelis-p in #15752
  • fix(dashboard): fixes to sidebar styling for layout configuration by @shahednasser in #15985
  • fix(core-flows): correctly clear a region payment providers by @NicolasGorga in #15986
  • fix(eslint-plugin): remove use-query-context-utility rule by @adem-loghmari in #15850
  • fix(file-s3,file-local): decode upload content by MIME type to stop binary file corruption by @BIGSUS24 in #15811
  • fix(utils): fix delete operation when primary key is not id by @shahednasser in #15989
  • fix(utils): apply select in strategy for single entity fetches by @shahednasser in #15990
  • fix(utils): prevent nested mikroOrm serialization from mutating parent keys by @dosacha in #15806
  • fix(payment, medusa): reject refunds and captures of negative or 0 amount by @shahednasser in #16036
  • fix(medusa): use email compatible validation with zod by @shahednasser in #16055
  • fix(utils): stop double-counting tax on discounts in refundable_total for non-tax-inclusive items by @shafi-VM in #15886
  • fix(framework): fix middlewares regex matcher by @shahednasser in #16066
  • fix(test-utils): replace unmaintained pg-god with @medusajs/framework/pg by @shafi-VM in #15887
  • fix(modules-sdk, framework): fail early when can't connect to the database by @shahednasser in #16100
  • fix(framework): register HTTP compression middleware by @AkashRanjan18 in #15957
  • fix(js-sdk): handle blocked browser storage by @Arunsiva003 in #15941
  • fix(core-flows): only complete cart from payment webhook on definitive actions by @rodriguescarson in #15930
  • fix(utils): assign per-adjustment subtotal/total instead of cumulative sum by @Venkat-jaswanth in #16013
  • fix(dashboard): don't mark promotions with per-attribute campaign budgets as expired by @MahinAnowar in #16018
  • fix(dashboard): sorting countries by name or code no longer throws error by @andrewaltos in #16020
  • fix: add missing keys to Persian (fa) translation file by @rasa7x in #16071
  • fix(utils): add BigNumber.toString() to avoid '[object Object]' coercion by @Venkat-jaswanth in #16014
  • fix(dashboard): handle empty price rules on price list price updates by @MahinAnowar in #15942
  • fix(caching-redis): read ioredis options from redisOptions for consistency with other Redis modules by @mvanhorn in #16101
  • fix(core-flows): filter reservations by line item in createOrderFulfillmentWorkflow by @andrewgreenh in #16136
  • fix(framework, utils): defensive handling of dotted path segments by @shahednasser in #16123
  • fix(framework, types, medusa): add disallowed query config to restrict retrieved fields by @shahednasser in #16125
  • fix(order): version-scope shipping method adjustments in select-in (list) path by @nbwar in #15920
  • fix(create-medusa-app): fix ajv error in npm installation by @shahednasser in #16124
  • fix(product): emit PRODUCT_CATEGORY_DELETED event on soft delete by @adem-loghmari in #16126
  • fix(js-sdk): avoid Vite import-analysis conflict with Product.import method by @ashif323 in #15899
  • fix(dashboard): allow same conditional shipping price amount for different conditions by @Ligament in #16130
  • fix(dashboard): fix edit reservartion form not showing anything by @shahednasser in #16132
  • fix(payment,promotion): serialize concurrent money guards to prevent over-capture, over-refund and budget overspend by @Meliceanu in #16097
  • fix(modules-sdk): validate link uniqueness on batch links being created by @shahednasser in #16144
  • fix(core-flows): refresh cart tax lines on any tax-relevant shipping address change by @shahednasser in #16035
  • fix(dashboard): prevent product option badges from overlapping with long option values by @khchen-doit in #15902
  • fix(loyalty): honor configured admin auth type in admin SDK by @shahednasser in #16164
  • fix: Support cross module filtering on belongs to foreign key by @sradevski in #16167
  • fix(framework): load workflows from index.[js,ts] files in worker mode by @MahinAnowar in #15702
  • fix(utils): apply onUpdate hooks before nativeUpdateMany by @sawirricardo in #15694
  • fix(eslint-plugin): normalize Windows paths in module relationship rule by @gaoflow in #15784
  • fix(core-flows): pass customer groups to pricing context in createCartWorkflow by @Kelpy2004 in #15746
  • fix(admin,utils): add AOA currency and guard region editor against unknown currencies by @BIGSUS24 in #15797
  • fix(core-flows): translate line items when creating an order with a l… by @shafi-VM in #15804
  • fix(medusa): return 400 instead of 500 for invalid promotion rule attribute with a value filter by @shafi-VM in #15819
  • fix(rbac): add missing migration for RbacRoleInheritance model by @pevey in #15833
  • fix(medusa,utils): defineConfig typed modules by @leobenzol in #15834
  • fix(dashboard): use total count instead of page length for product type products table pagination by @Tusharkhadde in #15950
  • fix: skip self-join for readonly links in cross-module filters by @peterlgh7 in #15968
  • fix(core-flows): preserve data field in prepareTaxLinesData during cart completion by @nam-stx in #15969
  • fix(core-flows): only emit RETURN_RECEIVED when receive_now is true by @d3v07 in #15856
  • fix: resolve Dependabot security update failure for @opentelemetry/core by @shahednasser with @Copilot in #16082
Documentation
Chores
Other Changes
New Contributors
  • @MahinAnowar made their first contribution in #15702
  • @BIGSUS24 made their first contribution in #15796
  • @Kelpy2004 made their first contribution in #15746
  • @nam-stx made their first contribution in #15840
  • @d3v07 made their first contribution in #15856
  • @dosacha made their first contribution in #15806
  • @vansh17June made their first contribution in #16058
  • @AkashRanjan18 made their first contribution in #15957
  • @Arunsiva003 made their first contribution in #15941
  • @rodriguescarson made their first contribution in #15930
  • @Venkat-jaswanth made their first contribution in #16013
  • @andrewaltos made their first contribution in #16020
  • @rasa7x made their first contribution in #16071
  • @andrewgreenh made their first contribution in #16136
  • @nbwar made their first contribution in #15920
  • @diwberg made their first contribution in #16015
  • @vssavosko made their first contribution in #15191
  • @KMLnk made their first contribution in #15948
  • @Meliceanu made their first contribution in #16097
  • @colinhacks made their first contribution in #15898
  • @khchen-doit made their first contribution in #15902

Full Changelog: v2.17.2...v2.18.0

View originalPermalink
How v2.18.0 went

v2.17.2

Added 4
  • Admin dashboard layout is now fully customizable through a reworked Layout Composer with drag-and-drop rearrangement of Topbar, Sidebar, Settings Sidebar and pages, with persistence in the database
  • Support for asynchronous payment methods where payment is confirmed after the initial request through provider webhooks
  • Quantity-based tiered pricing for price list prices directly in the admin
  • Contextual browser tab titles for admin dashboard pages showing section names for list pages and entity names for detail pages
Changed 1
  • Widget injection zones `.after` and `.before` no longer control placement within injection zones in LayoutComposer controlled pages; placement must be configured through the Editor view instead
Fixed 7
  • Handle async payment method check gracefully in Stripe webhook
  • Keep customer account-holder link on a failed payment-session initialization
  • Allow alias override in defineLink InputOptions to support linking the same module twice
  • Make custom_display_id searchable on orders
  • Product option and product option values are now translatable
  • Exclude gift cards from tax calculation and add an option for custom gift card codes
  • Move the remote joiner class to the modules SDK package

From medusa

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.

#15721 #15861 #15862


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.

#15085


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.

https://github.com/user-attachments/assets/1521c033-aee9-4fca-a2b8-d09ea2a95e2e

#15258


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 CustomPage

#14426

Features
  • 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
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

View originalPermalink
How v2.17.2 went

v2.17.1

Changed 1
  • Complete Japanese dashboard translations by filling 511 missing keys
Fixed 2
  • Fix event-bus-redis onApplicationStart hook to not await bullWorker_.run() which was causing application startup to hang indefinitely in worker mode
  • Properly handle undefined bullWorker in event-bus-redis

From medusa

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 (https://github.com/taskforcesh/bullmq/issues/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
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

View originalPermalink
How v2.17.1 went

v2.17.0

Added 11
  • Product options can now be defined globally at the store level and reused across multiple products instead of being recreated independently for each product
  • New @medusajs/eslint-plugin package with first-party linting rules for API routes, subscribers, scheduled jobs, admin customizations, and module patterns
  • Medusa CLI now runs linting automatically via npx medusa lint when the ESLint plugin is installed
  • ESLint plugin includes config presets (recommended, modules) for different levels of strictness
  • Add line_item_metadata to order responses
  • Allow already registered actor to accept admin invite in dashboard
Changed 3
  • Auth verification has been reworked into a flexible, provider-based system with the authVerificationsPerActor config to declare required verifications per actor type and auth provider
  • Auth verification endpoints moved from POST /auth/:actor_type/:auth_provider/verification/request and /confirm to flat POST /auth/verification/request and POST /auth/verification/confirm with updated request body parameters
  • Auth verification database schema replaced with a new auth_verification table; a database migration is required and any pending unconfirmed verifications are discarded
Fixed 2
  • Handle bodyparser errors
  • Run linting by default if eslint plugin is installed

From medusa

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.

#13817

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 flat POST /auth/verification/request and POST /auth/verification/confirm. The request route is now authenticated and takes entity_id, entity_type, and code_provider in 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_token table with a new auth_verification table. Run npx medusa db:migrate after upgrading; any pending (unconfirmed) verifications are discarded.

#15696


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 lint

Rules 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
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
Chores
New Contributors

Full Changelog: v2.16.0...v2.17.0

View originalPermalink
How v2.17.0 went

v2.16.0

Added 1
  • Add @medusajs/eslint-plugin to configuration; medusa build and medusa develop now run linting by default
Changed 6
  • Bump all @medusajs/* packages to v2.16.0
  • Bump MikroORM to 6.6.14 with security fix for CVE-2026-44680; manager.find now throws on non-existent entity relations instead of silently ignoring them
  • Bump react-router-dom to 6.30.4 for defensive security update
  • Replace emailpass provider's require_verification boolean option with http.authVerificationsPerActor configuration
  • Move email verification trigger from registration to login flow with new actor-agnostic routes
  • Update JS SDK email-verification signatures for auth.register, auth.login, auth.verification.request, and auth.verification.confirm
Removed 1
  • Remove verification routes /auth/[actor]/[provider]/verification/request and /auth/[actor]/[provider]/verification/confirm in favor of /auth/verification/request and /auth/verification/confirm
Security 1
  • Remove default JWT and cookie secret fallback 'supersecret'; production deployments now require http.jwtSecret and http.cookieSecret to be explicitly set or the application fails to start

From medusa

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.0

It will fetch the necessary changes needed to update your project.

<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 verificationRequestedHandler({
     event: { data: { entity_id: email, entity_type, code } },
     container,
   }: SubscriberArgs<{
     entity_id: string
     entity_type: string
     code_provider: string
     auth_identity_id: string
     code: string
     expires_at: string
     metadata?: Record<string, unknown>
   }>) {
     // only handle email verifications.
     if (entity_type !== "email") {
       return
     }
     // ...verification_url uses `code` instead of `token`
   }
   ```
   Update every reference inside the handler that used `token` to use `code` (including the `verification_url` built for the notification).

8. **Plan verification route migration.** Find any backend custom code, middleware, or storefront calls referencing the removed routes:
   - `/auth/[actor]/[provider]/verification/request` → `/auth/verification/request`
     - New request body: `{ entity_id: string, entity_type: string, code_provider?: string, metadata?: Record<string, unknown> }`. Response: `{ verification }`.
   - `/auth/[actor]/[provider]/verification/confirm` → `/auth/verification/confirm`
     - New request body: `{ code: string, code_provider?: string }`. Response: `{ entity_id, entity_type, code_provider, verified_at }`.

9. **Plan JS SDK signature migration (storefront / SDK consumers).** Update calls to match v2.16.0:
   - `auth.register(actor, method, payload)` — the previous `options` / `returnVerification` parameter is removed. The register call no longer reports whether verification is required.
   - `auth.login(actor, method, payload)` — now may return `{ verification_required: true, verification?, token }`. Verification is detected here, not at registration.
   - `auth.verification.request(body)` — signature changed to a single body object: `{ entity_id, entity_type, code_provider?, metadata? }` (no more `actor`/`method` positional args).
   - `auth.verification.confirm(body)` — signature changed to `{ code, code_provider? }` (token is passed as `code`).

   Migration examples:
   ```ts
   // Before
   const { verification_required } = await sdk.auth.register("customer", "emailpass", payload, { returnVerification: true })
   // After
   await sdk.auth.register("customer", "emailpass", payload)
   const loginResult = await sdk.auth.login("customer", "emailpass", payload)
   // loginResult may be { verification_required, token } when verification is needed

   // Before
   await sdk.auth.verification.request("customer", "emailpass", { entity_id: "customer@gmail.com" })
   await sdk.auth.verification.confirm("customer", "emailpass", { token })
   // After
   await sdk.auth.verification.request({ entity_id: "customer@gmail.com", entity_type: "email" })
   await sdk.auth.verification.confirm({ code: token })
   ```

10. **Plan storefront verification flow migration.** Only if a storefront with email verification exists. The flow moved verification from after registration to after login:
   - **Old flow:** register → register response says verification required → request verification → confirm → create customer + login.
   - **New flow:** register (get registration token) → redirect to login → login → login response says `verification_required` → call `/auth/verification/request` → user opens verify page with token → `/auth/verification/confirm` with `{ code: token }` → login again → create customer if needed → login again.
   - Plan the storefront page/route changes to implement the new ordering and the new SDK signatures from steps 8–9. If the storefront is a Medusa starter, note that updated starters already include this flow and the user may prefer to diff against the latest starter.

11. **Compile and present the migration plan** (see `<output_format>`). Then stop and wait for the user's approval. Do not edit any files until the user approves. If the user approves, apply the changes in dependency order (version bump and install first, then config, then code), and after each significant change note how to verify it.
</steps>

<constraints>
- Produce a plan first; make no file edits until the user explicitly approves it.
- Cover only the required/breaking changes listed above. Do not implement additive features (tax line context hook, multi-shipping carts, injection zones) even if you notice opportunities — mention them only if the user asks.
- Never hardcode or generate secret values into config or committed files. JWT/cookie secrets must come from environment variables; instruct the user to set strong values.
- Verify every API, route, config key, and SDK signature against https://docs.medusajs.com or the Medusa MCP server before relying on it. Do not guess.
- Match the project's existing code style (this is a TypeScript Medusa project: no semicolons, double quotes, 2-space indentation, parens on arrow functions). Never use emojis.
- If the storefront is not in this workspace, do not fabricate edits to it — surface the storefront steps as instructions for the user to apply in their storefront repo.
- Detect the package manager from the lockfile and use it consistently; do not assume npm.
</constraints>

<error_handling>
- If you cannot determine whether the project uses email verification, the package manager, or which actor types need verification, ask the user a targeted question rather than guessing.
- If a referenced file (e.g. `medusa-config.ts`, `tsconfig.json`) is missing or has an unexpected shape, report what you found and ask how to proceed instead of forcing an edit.
- If `manager.find` usage is ambiguous (e.g. dynamic field lists), flag it for manual review in the plan rather than rewriting it blindly.
- If the current version is already >= 2.16.0, report that and stop; do not downgrade or re-apply migrations.
</error_handling>

<output_format>
Present the plan as Markdown with these sections:

1. **Project summary** — shape (standalone/monorepo), current Medusa version, storefront presence, package manager.
2. **Applicable changes** — a checklist table: each in-scope change, whether it applies to this project (Yes/No/N/A), and a one-line reason.
3. **Proposed changes, in order** — for each applicable change: the files affected, the concrete edit (with before/after snippets where useful), and the command(s) to run.
4. **Must-fix before deploy** — items that will break a production boot or build if skipped (e.g. missing JWT/cookie secrets, lint errors blocking `medusa develop`).
5. **Manual verification steps** — how the user confirms each change worked after applying (e.g. `medusa build`, `medusa lint`, register/login/verify a test customer end-to-end).
6. **Out of scope / notes** — additive features intentionally skipped, and any storefront steps the user must apply in a separate repo.

End with an explicit line asking the user to approve the plan before you apply any changes.
</output_format>

<success_criteria>
- The plan covers every in-scope breaking change that applies to the project, and explicitly marks the rest N/A with a reason.
- No file is edited before user approval.
- Every proposed config key, route path, and SDK signature matches v2.16.0 (verified against the docs/MCP, not assumed).
- Secrets are handled via environment variables, never hardcoded.
- After approval and application, `medusa build` succeeds, `medusa lint` reports no errors, and (if email verification is used) a test register → login → verify → login flow completes end-to-end.
</success_criteria>
ESLint Plugin

To make development easier with Medusa, both for developers and AI agents, we've added a new ESLint plugin for Medusa applications. It catches issues that are either against Medusa conventions or may cause runtime errors if undetected.

All new Medusa projects come with the ESLint plugin installed by default. For existing users, we recommend installing it in your project as well. You can install the plugin without updating to this version of Medusa.

Learn more about the plugin in this documentation.

Install ESLint Plugin

To use the plugin, install it as a dev dependency along with ESLint-related plugins. For monorepo projects, install it in the root of the monorepo. For standalone Medusa projects, install it directly in the project:

// or use your package manager
pnpm install --save-dev @medusajs/eslint-plugin eslint jiti
Configure ESLint Plugin in Monorepo Projects

In your monorepository root, create eslint.config.ts with the following content:

import { defineConfig } from "eslint/config"
import medusa from "@medusajs/eslint-plugin"

export default defineConfig([...medusa.configs.recommended])

Then, create the file apps/backend/eslint.config.ts with the following content:

import { defineConfig } from "eslint/config"
import medusa from "@medusajs/eslint-plugin"

export default defineConfig([...medusa.configs.recommended])

Also, add eslint.config.* to the excluded files in apps/backend/tsconfig.json to avoid type errors on the file:

{
  "exclude": [
    // ...
    "eslint.config.*",
  ]
}
Configure ESLint Plugin in Standalone Project

In your standalone project (and plugins), create eslint.config.ts with the following content:

import { defineConfig } from "eslint/config"
import medusa from "@medusajs/eslint-plugin"

export default defineConfig([...medusa.configs.recommended])
Lint Project

The linting now works with eslint and your IDE if you have the ESLint plugin installed. If you upgrade to this version, you can also use Medusa's CLI for linting as explained in the next section.

Linting in Medusa Commands

🚧 Breaking change

If you setup the ESLint plugin and config in your Medusa backend, linting will run by default in the medusa build and medusa develop commands. If there are lint errors, both commands will fail.

You can disable the linting with the --no-lint flag on both commands:

pnpm medusa build --no-lint
pnpm medusa develop --no-lint

This release also includes a new lint command that runs the linting based on your configurations:

pnpm medusa lint

You can also pass options like --fix and --quiet.

Learn more in the CLI documentation.

MikroORM version bump to 6.6.14

🚧 Breaking change

MikroORM 6.6.12 is affected by CVE-2026-44680. This doesn't impact Medusa applications by default, but can affect Medusa customizations if they're JSON inputs are not properly validated with Zod.

This version updates MikroORM to 6.6.14. This comes with a breaking change in how MikroORM's manager.find method works. Previously, it ignored all relations that don't exist on an entity. Now, it will fail with an error.

This has been resolved internally by this PR. However, if you use manager.find in your code, we encourage you to look at the changes made in the PR and apply them in your code.

react-router-dom update

🚧 Breaking change

react-router-dom has also been updated to 6.30.4 due to a recent advisory. The previous version didn't impact Medusa applications; the update is a defensive measure only.

If you have react-router-dom installed in your application, make sure to update it to 6.30.4. Otherwise, admin customizations may break.

Email Verification Changes

🚧 Breaking change

Email verification has been expanded to support for general verification for any user information, such as email or phone number. Also, it now supports specifying different verification requirement per actor type.

In the previous version, The require_verification option of the Emailpass provider accepted a boolean value to enable or disable verification for all actor types. Now, this option has been removed. Instead, use the http.authVerificationsPerActor:

// medusa-config.ts

module.exports = defineConfig({
  projectConfig: {
    http: {
      authVerificationsPerActor: {
        user: [], // No verification required for users
        customer: [
          {
            entity_type: "email",
            auth_provider: "emailpass",
          }
        ]
      },
    },
    // ...
  },
  // ...
})

In this example, you require verification only for customers. The verification is for their email.

Learn more about setting up email verification in the backend in this documentation

Email Verification Flow Change

🚧 Breaking change

Following the above change, the email verification flow has now changed.

The default Medusa starter (dtc-starter) has been updated to include this new flow in the storefront by default.

In the previous version, you implemented the following flow for email verification in the storefront:

  1. Send a request to /auth/customer/emailpass/register to obtain a registration token.
  2. Request returns that the verification is required.
  3. Send a request to the /auth/customer/emailpass/verification/request route to request a verification token for the customer.
  4. The customer goes to the verification page in the storefront, with the token in the query parameter
  5. Send a request to the /auth/customer/emailpass/verification/confirm route to confirm the verification
  6. If verification is successful, create customer + login

In this version, the flow has changed to the following:

  1. Send a request to /auth/customer/emailpass/register to obtain a registration token.
  2. Request returns a successful response. Redirect the customer to the login page.
  3. Customer logs in. Send a request to /auth/customer/emailpass.
  4. Request returns that the verification is required.
  5. Send a request to the /auth/verification/request to request a verification token for the customer.
  6. The customer goes to the verification page in the storefront, with the token in the query parameter
  7. Send a request to the /auth/verification/confirm route to confirm the verification
  8. If verification is successful, create customer + login

Learn more about this new flow and how to implement it in this documentation

Change in Verification Routes

🚧 Breaking change

Following the above change in email verification flow, the following routes have been removed:

  • /auth/[actor]/[provider]/verification/request
  • /auth/[actor]/[provider]/verification/confirm

They've been replaced by the following routes:

  • /auth/verification/request
  • /auth/verification/confirm

Refer to the Admin and Store API references to learn more about these routes.

Change in JS SDK signature for email verification

🚧 Breaking change

The previous version introduced a new options parameter for the JS SDK's auth.register method that allows to configure the behavior when a registering user requires verification first. This option has been removed since the register route no longer returns whether verification is required, as explained in the verification flow changes section.

Instead, the sdk.auth.login route returns whether verification is required now. For example:

// Before
const { verification_required } = await sdk.auth.register(
  "customer",
  "emailpass",
  {
    email: "customer@gmail.com",
    password: "supersecret"
  },
  {
    returnVerification: true
  }
)

// After
// get registration token
await sdk.auth.register(
  "customer",
  "emailpass",
  {
    email: "customer@gmail.com",
    password: "supersecret"
  },
)

// later, login
const { verification_required } = await sdk.auth.login(
  "customer",
  "emailpass",
  {
    email: "customer@gmail.com",
    password: "supersecret"
  },
)

In addition, the signatures of the auth.verification.request and auth.verification.confirm have changed due to the changes to their routes:

// before
await sdk.auth.verification.request("customer", "emailpass", {
  entity_id: "customer@gmail.com"
})
await sdk.auth.verification.confirm("customer", "emailpass", {
  token
})

// after
await sdk.auth.verification.request({
  entity_id: "customer@gmail.com",
  entity_type: "email",
})
await sdk.auth.verification.confirm({ code: token })
Tax Line Context Hook

A new setTaxLineContext hook allows customization of tax calculation context, providing greater flexibility for complex tax scenarios. The hook is added to the cart's upsertTaxLinesWorkflow and updateTaxLinesWorkflow workflows, and the order's updateOrderTaxLinesWorkflow.

For example:

import { updateOrderTaxLinesWorkflow } from "@medusajs/medusa/core-flows";

updateOrderTaxLinesWorkflow.hooks.setTaxLineContext(
  async ({ order, items, shipping_methods }, { container }) => {
    // do something
  }
)
Multi-Shipping Method Cart Support

Carts now support multiple shipping methods of different shipping profiles, enabling split shipments and complex fulfillment scenarios. This allows customers to choose different shipping options for different items within the same order.

You can now pass an array of shipping methods to add to the cart in the /store/carts/:id/shipping-methods API route:

sdk.store.cart.addShippingMethod("cart_123", [
   // option 1 with shipping profile 1
   {
    option_id: "so_123",
    data: {
      // custom data for fulfillment provider.
    }
  },
   // option 2 with shipping profile 2
   {
    option_id: "so_456",
    data: {
      // custom data for fulfillment provider.
    }
  }
])
New Injection Zones

This release adds the following new injection zones in the admin dashboard:

// draft order pages
"draft_order.details.before"
"draft_order.details.after"
"draft_order.details.side.before"
"draft_order.details.side.after"
"draft_order.list.before"
"draft_order.list.after"

// gift card pages
"gift_card.details.before"
"gift_card.details.after"
"gift_card.details.side.before"
"gift_card.details.side.after"
"gift_card.list.before"
"gift_card.list.after"
"gift_card.list.side.before"
"gift_card.list.side.after"

// store credit account pages
"store_credit_account.details.before"
"store_credit_account.details.after"
"store_credit_account.details.side.before"
"store_credit_account.details.side.after"
"store_credit_account.list.before"
"store_credit_account.list.after"
Custom Injection Zones

The admin dashboard now supports adding custom injection zones in custom pages. This allows plugin developers to add widget injection zones in their UI routes.

Learn more in the Custom Injection Zones documentation.

Other dependency patches

This release bumps dependencies for OpenTelemetry and UUID due to recent security advisories in these packages. Medusa applications before this version are not impacted by these advisories, as we don't use any of the affected code, so this is a defensive measure only.

Tighten Payment webhook handling

This release handles edge cases when a payment provider's getWebhookActionAndData returns an undefined data.session_id field. Previously, this led to an arbitrary payment session being processed. Now, webhook handling exists early.

Properly escape JSON containment filters with Index Module

This release properly escapes single quotes in filters passed to the /store/products route when the Index Module is used. Previously, passing single quotes in filters like title caused SQL syntax errors. Now, single quotes are escaped in those filters.

Removed default JWT and Cookie secrets

This release removes the default supersecret value set for JWT and cookie secrets. In production, if those keys aren't set manually, the application will throw an error and fail to start.

Features
Bugs
  • fix(cli): replace unmaintained pg-god with direct pg.Pool CREATE DATA… by @dsumeet14 in #15635
  • fix: product organization combobox search and cache invalidation by @PrathamRanka in #15634
  • fix(dashboard): enforce locale-specific plural forms in translations by @radeknapora in #13268
  • fix(index): subscribe to restored events and re-index on restore by @Tusharkhadde in #15654
  • fix(payment-stripe): warn when webhookSecret is missing by @dsumeet14 in #15684
  • fix(dashboard): fix refund form rounding for sub-cent amounts by @Metbcy in #15349
  • fix(dashboard): prevent duplicate items in return create form by @Metbcy in #15348
  • fix: pin peter-evans/create-pull-request to a commit SHA (supply-chain hardening) by @eliottreich in #15664
  • fix(js-sdk): add RBAC ESM import extensions by @puneetdixit200 in #15597
  • fix(core-flows): skip tax on gift card line items when cart taxes are recalculated by @cainydev in #15545
  • fix(utils): InjectManager error message shows "undefined" instead of "baseRepository_" by @Tusharkhadde in #15662
  • fix(core-flows): apply customer-group price lists when creating a dra… by @shafi-VM in #15538
  • fix(medusa): allow updating buyget promotions via PATCH /admin/promotions/:id by @sanidhyasin in #15619
  • fix(medusa): type product category create response by @Zbl1007 in #15588
  • fix: correct "occured" → "occurred" typo in error messages and docs by @DucMinhNe in #15549
  • fix(product): Support non-Latin characters in product handles with Un… by @Cpidar in #15346
  • fix(admin): fix Copy API Key behavior in secret key created dialog by @Tusharkhadde in #15531
  • fix(plugin:develop): invoke yalc via process.execPath on Windows by @addemod in #15523
  • fix(medusa): clear session cookie on DELETE /auth/session by @shafi-VM in #15510
  • fix(medusa): include address_name in customer address defaults by @puneetdixit200 in #15511
  • fix(utils): preserve context index overrides by @puneetdixit200 in #15503
  • fix(core-flows): include shipping_method.name in tax calculation context by @NicolasGorga in #15575
  • fix(index): use parameterized binding for JSONB containment filter by @shahednasser in #15599
  • fix(framework): change for session cookies by @shahednasser in #15601
  • fix(utils,product): handle array of mikroORM entities serialization and fix variant image assignment serialization issue by @NicolasGorga in #15591
  • fix: use unique order country column id by @xonaib in #15486
  • fix: support csv mime types by @aritradhabal in #15483
  • fix(dashboard): preserve zero-amount shipping option prices on re-save by @Ultron03 in #15468
  • fix(dashboard): prevent crash when deleting the last image from product media gallery by @appinteractive in #15444
  • fix(file-local, core-flows): improve file resolution + invalid csv file handling by @shahednasser in #15558
Documentation
Chores
Other Changes
New Contributors
  • @dsumeet14 made their first contribution in #15635
  • @PrathamRanka made their first contribution in #15634
  • @archievi made their first contribution in #15637
  • @radeknapora made their first contribution in #13268
  • @Tusharkhadde made their first contribution in #15654
  • @NicolasGorga made their first contribution in #15685
  • @Metbcy made their first contribution in #15349
  • @eliottreich made their first contribution in #15664
  • @puneetdixit200 made their first contribution in #15597
  • @cainydev made their first contribution in #15545
  • @shafi-VM made their first contribution in #15538
  • @sanidhyasin made their first contribution in #15619
  • @leobenzol made their first contribution in #15595
  • @Zbl1007 made their first contribution in #15588
  • @GBreg19 made their first contribution in #15561
  • @DucMinhNe made their first contribution in #15549
  • @Jeri436 made their first contribution in #15543
  • @Cpidar made their first contribution in #15346
  • @addemod made their first contribution in #15523
  • @christiananese made their first contribution in #15584
  • @xonaib made their first contribution in #15486
  • @aritradhabal made their first contribution in #15483
  • @bodasooqa made their first contribution in #15567
  • @Ultron03 made their first contribution in #15468
  • @appinteractive made their first contribution in #15444

Full Changelog: v2.15.5...v2.16.0

View originalPermalink
How v2.16.0 went

v2.15.5

Added 4
  • Support multi-factor authentication (MFA) with admin dashboard UI for setting up and managing authentication methods
  • Emit MFA lifecycle events for tracking authentication flows
  • Add emailpass email verification primitives
  • Add RBAC admin dashboard utilities
Fixed 6
  • Avoid refunding captures made in separate completeCartWorkflow executions
  • Respect allow_backorder when calculating pickup inventory availability
  • Allow cancelling pending MFA setup
  • Order list status badges show correct colors when view_configurations is enabled
  • Use hasPermission util to perform checks in validateUserRolePermissionsStep
  • Align validate user permissions check with hasPermission util

From medusa

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_string

Also, 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.

#15496 #15493 #15495

Features
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
Chores

Full Changelog: v2.15.3...v2.15.5

View originalPermalink
How v2.15.5 went

v2.15.3

Added 3
  • Add primitives to support Multi-Factor Authentication (MFA) including authentication provider primitives, API routes for MFA management, and retrieval functionality
  • Add MFA auth helpers to js-sdk
  • Add CodeInput component to ui
Changed 2
  • Surface information when promotion codes are skipped due to budget or usage limits to provide visibility into why certain promotions weren't applied
  • Broaden React peer dependencies in design-system to support v18
Fixed 8
  • Harden create payment sessions when customer has no account holders
  • Include inventory query in detail key in dashboard
  • Complete and correct Thai (th) translations in dashboard
  • Encode URL credentials and fix schema-qualified RENAME TO in test-utils and link-modules
  • Fix filtering by categories and tags in /store/products with the index module
  • Fix incorrect command replacement when using yarn and npm in create-medusa-app
  • Implement tokenized free text search in utils
  • Fix incorrect stock location picked for item with backorder in a sales channel with multiple locations

From medusa

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.

#15396

Features
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
Chores

Full Changelog: v2.15.2...v2.15.3

View originalPermalink
How v2.15.3 went

v2.15.2

Added 2
  • Enable index when querying products via promotion attribute values
  • Enable SKU search in products entrypoint
Fixed 4
  • Fix migrations regression where running db:migrate generated a full database snapshot for custom modules, causing db:generate to override module snapshots and generate migrations that would drop all tables
  • Disable generating snapshots on running migrations
  • Build should throw if medusa-config throws
  • Add jitter to Redis lock retry backoff in locking-redis

From medusa

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 as product, order, etc... if so, delete those snapshots and regenerate again with medusa db:generate.

You can also use the following prompt with your AI agent:

# 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
Chores

Full Changelog: v2.15.1...v2.15.2

View originalPermalink
How v2.15.2 went

v2.15.1

Fixed 1
  • Include missing PRs from 2.15.0 release that were not included due to an issue during the release process

From medusa

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

Full Changelog: v2.15.0...v2.15.1

View originalPermalink
How v2.15.1 went

v2.15.0

Added 4
  • Add setPromotionContext hook to pass additional context for promotion computation
  • Add mcloud proxy command to medusa CLI for working with Medusa Cloud deployments
  • Add SKU search support to admin API for searching products by stock keeping unit
  • Add copy action for original translations on translations edit page in dashboard
Changed 1
  • Align width, length, height, and weight properties on Product and ProductVariant data models to be of type float instead of text and number respectively
Fixed 14
  • Expose metadata on refund creation through refundPaymentsWorkflow
  • Backfill missing inSchemaRef for existing parent entries in index
  • Flatten WorkflowData type to remove recursive expansion that caused TypeScript excessive stack depth errors
  • Mark Order computed status fields as non-filterable in settings
  • Preserve casing in toCamelCase for PascalCase identifiers with digits
  • Auto-select currency row when its tax-inclusive toggle is enabled in dashboard

From medusa

Highlights
Missing PRs in published packages

Due to an issue during the release process, we identified several PRs listed in these release notes missing from the published packages. We recommend installing 2.15.1 instead.

Aligned Product and Variant Attribute Types

🚧 Breaking change

This version aligns the width, length, height, and weight properties on the Product and ProductVariant data models to be of type float. In previous versions, their type was text on the Product data model, and number on the ProductVariant data model.

This change only affects customizations that relied on the previous text type for these attributes, which are now expected to be float values.


Medusa Cloud Proxy Command

The Medusa CLI now includes a mcloud proxy command for working with Medusa Cloud deployments, streamlining local development workflows against cloud environments.

medusa mcloud proxy

#15320


Promotion Context Hooks

The promotion computation system now supports custom context hooks, allowing developers to pass additional context data when calculating promotions. This enables more sophisticated promotion rules based on external data sources or custom business logic.

Example:

// in src/workflows/hooks/promotion.ts
import { StepResponse } from "@medusajs/framework/workflows-sdk";
import { updateCartPromotionsWorkflow } from "@medusajs/medusa/core-flows";

updateCartPromotionsWorkflow.hooks.setPromotionContext(
  ({ cart }, { container }) => {
    if (cart.items.length >= 2) {
      return new StepResponse({
        company_id: "company_123"
      });
    }
  }
);

#15301


Product SKU Search Support

The admin API now supports searching products by SKU, making it easier to find and manage products through their stock keeping unit identifiers.

#13930


Translation Copy Actions

The admin dashboard translation edit page now includes copy actions for original translations, improving the translation workflow by allowing translators to quickly copy base text.

#14943

Features
  • feat(core-flows): add setPromotionContext hook to pass additional context for promotion computation by @NicolasGorga in #15301
  • feat: Add mcloud proxy command to medusa CLI by @sradevski in #15320
  • feat(product): add SKU search support to admin API by @bqst in #13930
  • feat(dashboard): add copy action for Original translations on translations edit page by @LukasKri in #14943
  • fix(core-flows, payment, types): expose metadata on refund creation through refundPaymentsWorkflow by @Metbcy in #15273
Bugs
  • fix(index): backfill missing inSchemaRef for existing parent entries by @ranma-dev in #15131
  • fix(workflows-sdk): flatten WorkflowData type to remove recursive expansion that caused TS excessive stack depth errors in large consumer codebases by @NicolasGorga in #15174
  • fix(settings): mark Order computed status fields as non-filterable by @aliaksei-loi in #15262
  • fix(utils): preserve casing in toCamelCase for PascalCase identifiers with digits by @Chicolll in #15293
  • fix(dashboard): auto-select currency row when its tax-inclusive toggle is enabled by @Anexus5919 in #15164
  • fix(core-flows): populate delivery_address on return fulfillments by @ShriyansSharma27 in #15241
  • fix(core-flows): validate cart has items before completing by @rnagulapalle in #15231
  • fix(utils): skip empty string translations when applying locale by @asynchroza in #15013
  • fix(medusa): prevent plugin:db:generate crash when model files export by @shubhamchoudhary-2003 in #14718
  • fix(order): fix regression on order.items.metadata showing null when line item metadata is set by @NicolasGorga in #15329
  • fix(core-flows): apply currency-precision tolerance to refundPayments by @hunnyboy1217 in #15316
  • Fixes #14793 -- paymentService_ was not getting updated with the data causing the issue by @Ultron03 in #15295
  • fix(index,modules-sdk,): introduce locking to avoid race conditions in distributed systems for migrations, links and index partition creation by @NicolasGorga in #15176
  • fix(i18n): refresh Czech (cs) translations by @dugynoo in #15286
  • fix(pricing): calculatePrices() with empty context selects prices from pricelists that explicitly have rules by @ShriyansSharma27 in #15277
  • fix(product, dashboard): align product and variant volumetric attributes data types by @NicolasGorga in #14762
  • fix(core-flows,payment): Fix rounding issue on refund creation by @NicolasGorga in #15303
  • fix(run-scripts): load WorkflowLoader during db:migrate so workflow hooks are registered by @AKIB473 in #15260
  • fix(admin): correct settings-related routes in global search results by @biocodersin in #14853
  • fix(file-s3): encode URL path segments individually to preserve prefix slashes by @aayushbaluni in #15109
Documentation
Chores
New Contributors
  • @ranma-dev made their first contribution in #15131
  • @Anexus5919 made their first contribution in #15164
  • @ShriyansSharma27 made their first contribution in #15241
  • @asynchroza made their first contribution in #15013
  • @LukasKri made their first contribution in #14943
  • @shubhamchoudhary-2003 made their first contribution in #14718
  • @hunnyboy1217 made their first contribution in #15316
  • @Ultron03 made their first contribution in #15295
  • @dugynoo made their first contribution in #15286
  • @sradevski made their first contribution in #15320
  • @AKIB473 made their first contribution in #15260
  • @bqst made their first contribution in #13930
  • @biocodersin made their first contribution in #14853
  • @aayushbaluni made their first contribution in #15109
  • @Metbcy made their first contribution in #15273

Full Changelog: v2.14.2...v2.15.0

View originalPermalink
How v2.15.0 went
View all

Discussion

If you publish medusa, you can claim this product by proving you administer its repository.