github medusajs/medusa v2.18.0

7 hours ago

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

view-config-2.mp4

Transfer an Order to a Guest Customer

Orders can now be transferred to a guest customer from the admin dashboard without going through a full order transfer flow. This makes it possible to reassign an order to a guest account when it was placed under the wrong customer or needs to be detached from a registered account. The original customer transfer flow remains for transferring orders to registered customers.

#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

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

Don't miss a new medusa release

NewReleases is sending notifications on new releases.