Skip to content

Releases: medusajs/medusa

v2.11.3: Dependency clean-up

05 Nov 12:08

Choose a tag to compare

Highlights

Dependency clean-up and restructuring

This release comes with a major clean-up of dependencies used across our packages.

The clean-up includes 1) replacing large dependencies with native code to reduce bundle size, 2) ensuring unified dependency versions across packages, and 3) eliminating security vulnerabilities from dependencies.

As a result of these changes, the production node_modules have been reduced from 1GB+ → 837MB (approx. -16%), and we are down to a single low security vulnerability.

You can read more about the changes in #13932, #13910, and #13940.

Features

Bugs

Documentation

Chores

Other Changes

Full Changelog: v2.11.2...v2.11.3

v2.11.2: Product variant images and Index management API

31 Oct 09:27

Choose a tag to compare

Highlights

Product variant images

This release introduces images on product variants. It is now possible to assign images to individual product variants, enabling fully scoped variant-specific photos. In addition to this, a variant can have a thumbnail among its associated images.

The behavior of images is as follows:

  • Images are added to products
  • Variant images are chosen among the product images
  • Upon retrieving variants, the variant images will contain:
    • Product images that are not associated with any variants
    • Images that are associated with the variant

For example, let's imagine you have three product images:

  1. White t-shirt
  2. Black t-shirt
  3. T-shirt on a model

You want to share the third image across variants, and have the white image on the white variants and black image on the black variants. To achieve this, you associate the white t-shirt image with the white variants, and the black t-shirt image with the black variants. The third image should have no associated variants.

This in turn would result in the following response on variants:

{
  "id": "prod_1234",
  "variants": [
    {
      "id": "var_1234",
      "title": "White t-shirt"
      "images": [
        { "id": "img_1234", "url": "http://images.com/white-t-shirt.png" },
        { "id": "img_1234", "url": "http://images.com/t-shirt-on-model.png" }
      ]
    },
    {
      "id": "var_4321",
      "title": "Black t-shirt"
      "images": [
        { "id": "img_1234", "url": "http://images.com/black-t-shirt.png" },
        { "id": "img_1234", "url": "http://images.com/t-shirt-on-model.png" }
      ]
    },
  ]
}

Managing variant images

Two endpoints have been added:

  • POST /admin/products/:id/variants/:variant_id/images/batch — Batch manage image associations for a specific variant.
  • POST /admin/products/:id/images/:image_id/variants/batch — Batch manage variant associations for a specific image.

Admin UI

You can manage product variant images from the product page in the admin dashboard. There are two ways to manage these.

From the root-level product page, you can associate a single image with many variants:

CleanShot 2025-10-30 at 10 16 25@2x

From the variant-specific page, you can associate many images with a single variant:

CleanShot 2025-10-30 at 10 17 32@2x

Data model changes

The following are changes to the data models in the Product Module required to enable this feature:

  • ProductVariant now includes a many-to-many relationship with ProductImage.
  • A new ProductVariantProductImage pivot entity manages these associations.

Therefore, please make sure to run migrations after upgrading to the new version:

npx medusa db:migrate

Index management API

This release adds a new API for managing the index, enabling you to trigger synchronization and retrieve detailed information about the last sync. The update improves visibility and control over the indexing process.

API

  • GET /admin/index/details — Retrieve index metadata, including entity type, status, last synced key, and last update timestamp.
  • POST /admin/index/sync — Trigger synchronization from last synced data
    • Supports optional options:
      • { strategy: "full" } — Perform a full sync
      • { strategy: "reset" } — Truncate tables and perform a fresh sync.

Faster price updates

This release brings performance improvements to the Pricing Module, significantly speeding up price list and price set updates. The changes focus on optimizing database queries, reducing redundant computations, and streamlining price normalization and upsert operations.

Features

  • feat(core-flows,product,types): scoped variant images by @fPolic in #13623
  • feat(medusa,types): product variant store endpoints by @fPolic in #13730
  • feat(product): build variant images when retrieving product by @fPolic in #13731
  • feat(index): Add http/module api to interact with the index engine by @adrien2p in #13869
  • feat: Implement medusa payments provider by @sradevski in #13772
  • feat(dashboard): variant images list thumbnail + refactor form state management by @fPolic in #13905

Bugs

Documentation

Chores

  • chore(docs): Update version in documentation (automated) by @github-actions[bot] in #13854
  • chore(docs): Generated DML JSON files (automated) by @github-actions[bot] in #13855
  • chore(docs): Updated UI Reference (automated) by @github-actions[bot] in #13856
  • chore(docs): Generated References (automated) by @github-actions[bot] in #13857
  • chore: fixes to http and request types for products by @shahednasser in #13833
  • chore: fixes to HTTP and request types for API Keys by @shahednasser in #13819
  • chore: fixes to HTTP and request types for promotions by @shahednasser in #13820
  • chore: fixes to HTTP and request types for carts by @shahednasser in #13821
  • chore: fixes to http and request types for orders (2) by @shahednasser in #13823
  • chore: fixes to http and request types for orders (3) by @shahednasser in #13824
  • chore: fixes to http and request types for customers by @shahednasser in #13825
  • chore: fixes to http and request types for fulfillment by @shahednasser in #13826
  • chore: fixes to http and request types for inventory by @shahednasser in #13827
  • chore: fixes to http and request types for users by @shahednasser in #13828
  • chore: fixes to http and request types for payments by @shahednasser in #13830
  • chore: fixes to http and request types for payment and regions by @shahednasser in #13831
  • chore: fixes to http and request types for sales channes, taxes, and store by @shahednasser in #13832
  • chore: fixes to http and request types for orders (1) by @shahednasser in #13822
  • chore: Inject sandbox handle in cloud config by @olivermrbl in #13879
  • chore(types,notification): Make template nullable on emails by @olivermrbl in #13889

Other Changes

  • add Medusa Cloud Email provider by @peterlgh7 in #13781
  • feat(dashboard): add input field for tracking_url and label_url in shipment form by @kevinresol in #13787
  • fix(dashboard): Resolve issue with product attributes input by @siddarthan007 in #13863
  • fix(dashboard): tax region override too large payload by @NicolasGorga in #13860
  • fix(admin): fix admin promotion list sort by @bqst in #13872
  • feat(admin): change admin order list default sort by @bqst in #13874
  • fix(dashboar...
Read more

v2.11.1

25 Oct 19:59

Choose a tag to compare

Highlights

Fixes regression with listing price preferences

See issue and PR.

Adds version to credit lines

This version adds column version to order credit lines and is used for keeping track of credit lines during order edits.

This addition comes with a migration, so after upgrading run the following command to apply it:

npx medusa db:migrate

Features

Bugs

  • fix(test-utils): Duplicated subscribers re insertion by @adrien2p in #13798
  • fix: prevent jobId collisions on workflow step retries by @srindom in #13786
  • fix(medusa): images recreate on product update by @fPolic in #13813

Documentation

Chores

  • chore(docs): Update version in documentation (automated) by @github-actions[bot] in #13791
  • chore(docs): Generated References (automated) by @github-actions[bot] in #13793
  • chore(docs): Generated DML JSON files (automated) by @github-actions[bot] in #13790
  • chore(docs): Updated UI Reference (automated) by @github-actions[bot] in #13792
  • chore: fixes to refund reasons tsdocs by @shahednasser in #13796
  • chore(workflow-engine-*): cleanup and improvements by @adrien2p in #13789
  • chore: update description of emitEventStep by @shahednasser in #13838
  • chore: improve TSDocs for tax provider module by @shahednasser in #13846

Other Changes

New Contributors

Full Changelog: v2.11.0...v2.11.1

v2.11.0: Caching primitives, manual refunds, and improved promotion limits

21 Oct 07:57
33f788b

Choose a tag to compare

Highlights

Caching layer

This release adds new caching primitives to Medusa's toolbox.

1. New caching module, @medusajs/caching

We have added a new caching module in early preview with strong tools to integrate a cache into your Medusa application. The module comprises three key elements:

  • Cache service: the main module service with a simple API for interacting with the cache
  • Cache provider: the provider service responsible for delegating operations to the configured cache provider
  • Cache strategy: the strategy responsible for computing cache keys and invalidation tags

Cache invalidation
Knowing when and how to invalidate cache entries is a challenging task. With the new caching module, we have baked this logic directly into the tooling, so you don't have to manage the complexity yourself.

We plan to share a technical deep dive covering the invalidation mechanism in detail, but here's the short version: whenever entries are inserted, the cache strategy computes tags based on the data. Invalidation of these entries relies on internal module events emitted whenever mutational service methods are called, e.g. service.update({ ... }). The cache strategy intercepts these events, computes the relevant tags using the event data, and instructs the cache provider to clear all entries associated with those tags.

Read more about this in our documentation.

2. New Redis caching provider, @medusajs/caching-redis

As described above, the new caching module follows our existing provider architecture, allowing different technologies to be used interchangeably without affecting the core application. In this release, we’ve introduced a Redis caching provider, which integrates directly with the caching module and serves as the underlying storage layer for the cached data.

3. New cache option on Medusa Query

Finally, to use the cache, we have introduced a new cache option in our query.graph API:

const { data } = await query.graph({
  entity: "product",
  filters: { id: "prod_1234" },
  options: { cache: { enable: true } }
})

This new option allows you to leverage the full power of the cache without having to think about computing keys and invalidation tags. These are automatically computed when query.graph is configured to use the cache.

Installation
Since the caching module is still in early preview, you need to enable a feature flag to use it in addition to registering the Redis cache provider in your medusa-config.ts. The new packages come pre-installed in the latest version of @medusajs/medusa.

// medusa-config.ts
module.exports = defineConfig({
  projectConfig: { ... },
  modules: [
    {
      resolve: "@medusajs/medusa/caching",
      options: {
        providers: [
          {
            id: "caching-redis",
            resolve: "@medusajs/caching-redis",
            options: {
              redisUrl: process.env.REDIS_URL,
            },
          },
        ],
      },
    },
  ],
  featureFlags: {
    caching: true,
  },
});

By default, the cache is integrated across several business-critical APIs to boost performance and throughput. This includes all cart-related operations, where we cache the following data:

  • Regions
  • Promotion codes
  • Variant price sets
  • Variants
  • Shipping options
  • Sales channels
  • Customers

With caching enabled, we’ve observed performance improvements of up to 70% across these operations. Read our blog post
for detailed benchmarks
across different Medusa versions.

Bear in mind, our new caching tools are in preview, so they should be used with caution.

Bundle core Medusa dependencies

Breaking change

This release restructures the required dependencies of Medusa projects. We are consolidating all non-Medusa dependencies into a new @medusajs/deps package, which is installed internally in @medusajs/medusa. We are making this change to take control over our core dependencies. This will enable us to add new dependencies and do minor/major upgrades of core dependencies without breaking users' applications.

This is a breaking change, and the following steps are required to use versions 2.11.0 and over:

  1. Remove the following packages from dependencies in your package.json:
"dependencies": {
    "@medusajs/admin-sdk": "2.11.0",
    "@medusajs/cli": "2.11.0",
    "@medusajs/framework": "2.11.0",
    "@medusajs/medusa": "2.11.0",
-    "@mikro-orm/core": "6.4.3",
-    "@mikro-orm/knex": "6.4.3",
-    "@mikro-orm/migrations": "6.4.3",
-    "@mikro-orm/postgresql": "6.4.3",
-    "awilix": "^8.0.1",
-    "pg": "^8.13.0"
  },
"devDependencies": {
    "@medusajs/test-utils": "2.11.0",
-    "@mikro-orm/cli": "6.4.3",
  ...
  1. Install dependencies using your preferred package manager
  2. Run the included codemod (see section below)
  3. Start your application. If you have issues, please open a GitHub Issue and we will help you out as soon as possible

Codemod

By side effect, this update also requires you to update explicit imports from these dependencies in your own project. To help you with this, we have included a codemod that automatically replaces imports throughout your codebase.

To use the codemod, create a file replace-imports.js in the root of your Medusa application with the following content

Codemod
#!/usr/bin/env node

const fs = require("fs")
const path = require("path")
const { execSync } = require("child_process")

/**
 * Script to replace imports and require statements from mikro-orm/{subpath}, awilix, and pg
 * to their @medusajs/framework equivalents
 */

// Define the replacement mappings
const replacements = [
  // MikroORM imports - replace mikro-orm/{subpath} with @medusajs/framework/mikro-orm/{subpath}
  {
    pattern: /from\s+['"]@?mikro-orm\/([^'"]+)['"]/g,
    // eslint-disable-next-line quotes
    replacement: 'from "@medusajs/framework/mikro-orm/$1"',
  },
  // Awilix imports - replace awilix with @medusajs/framework/awilix
  {
    pattern: /from\s+['"]awilix['"]/g,
    // eslint-disable-next-line quotes
    replacement: 'from "@medusajs/framework/awilix"',
  },
  // PG imports - replace pg with @medusajs/framework/pg
  {
    pattern: /from\s+['"]pg['"]/g,
    // eslint-disable-next-line quotes
    replacement: 'from "@medusajs/framework/pg"',
  },
  // OpenTelemetry imports - replace @opentelemetry/instrumentation-pg, @opentelemetry/resources, 
  // @opentelemetry/sdk-node, and @opentelemetry/sdk-trace-node with @medusajs/framework/opentelemetry/{subpath}
  {
    pattern: /from\s+['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]/g,
    // eslint-disable-next-line quotes
    replacement: 'from "@medusajs/framework/opentelemetry/$1"',
  },
  // MikroORM require statements - replace require('@?mikro-orm/{subpath}') with require('@medusajs/framework/mikro-orm/{subpath}')
  {
    pattern: /require\s*\(\s*['"]@?mikro-orm\/([^'"]+)['"]\s*\)/g,
    // eslint-disable-next-line quotes
    replacement: 'require("@medusajs/framework/mikro-orm/$1")',
  },
  // Awilix require statements - replace require('awilix') with require('@medusajs/framework/awilix')
  {
    pattern: /require\s*\(\s*['"]awilix['"]\s*\)/g,
    // eslint-disable-next-line quotes
    replacement: 'require("@medusajs/framework/awilix")',
  },
  // PG require statements - replace require('pg') with require('@medusajs/framework/pg')
  {
    pattern: /require\s*\(\s*['"]pg['"]\s*\)/g,
    // eslint-disable-next-line quotes
    replacement: 'require("@medusajs/framework/pg")',
  },
  // OpenTelemetry require statements - replace require('@opentelemetry/instrumentation-pg'), 
  // require('@opentelemetry/resources'), require('@opentelemetry/sdk-node'), and 
  // require('@opentelemetry/sdk-trace-node') with require('@medusajs/framework/opentelemetry/{subpath}')
  {
    pattern: /require\s*\(\s*['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]\s*\)/g,
    // eslint-disable-next-line quotes
    replacement: 'require("@medusajs/framework/opentelemetry/$1")',
  },
]

function processFile(filePath) {
  try {
    const content = fs.readFileSync(filePath, "utf8")
    let modifiedContent = content
    let wasModified = false

    replacements.forEach(({ pattern, replacement }) => {
      const newContent = modifiedContent.replace(pattern, replacement)
      if (newContent !== modifiedContent) {
        wasModified = true
        modifiedContent = newContent
      }
    })

    if (wasModified) {
      fs.writeFileSync(filePath, modifiedContent)
      console.log(`✓ Updated: ${filePath}`)
      return true
    }

    return false
  } catch (error) {
    console.error(`✗ Error processing ${filePath}:`, error.message)
    return false
  }
}

function getTargetFiles() {
  try {
    // Get the current script's filename to exclude it from processing
    const currentScript = path.basename(__filename)
    
    // Find TypeScript/JavaScript files, excluding common directories that typically don't contain target imports
    const findCommand = `find . -name node_modules -prune -o -name .git -prune -o -name dist -prune -o -name build -prune -o -name coverage -prune -o -name "*.ts" -print -o -name "*.js" -print -o -name "*.tsx" -print -o -name "*.jsx" -print`
    const files = execSync(...
Read more

v2.10.3: View configurations and Shipping Options context hook

18 Sep 16:51

Choose a tag to compare

Highlights

[Experimental] Customizable columns in Medusa Admin tables

In previous versions, tables that list Orders, Products, etc., had hard-coded columns that couldn’t be modified. However, columns have different relevance between businesses. The only way to get tables to show additional columns was to introduce admin extensions that duplicated the original table’s behavior. This was tedious and clunky.

In this release, we are rolling out experimental support for View Configurations, which allow you to change which columns are displayed in the Orders and Products tables. Click the columns dropdown to see the available columns and add the ones you want. The available columns are computed using Medusa’s module graph, meaning any custom data models linked to Orders or Products can also be included in your table config.

With the View Configurations feature, we also enable saved views so you can have the right view at hand for your workflow. Saved views hold both your column configurations and any filters you have applied.

To try View Configurations turn on the feature flag in your medusa-config.ts

module.exports = defineConfig({
  ...
  featureFlags: {
    view_configurations: true
  }
})

Shipping options context hook

This release introduces a hook to customize the context used to list shipping options, allowing you to leverage custom rules for shipping options more easily.

The hook can be used like so:

import { listShippingOptionsForCartWithPricingWorkflow } from "@medusajs/medusa/core-flows"
import { StepResponse } from "@medusajs/workflows-sdk"

listShippingOptionsForCartWithPricingWorkflow.hooks.setShippingOptionsContext(
  async ({ cart }, { container }) => {

    if (cart.customer_id) {
      return new StepResponse({
        customer_id: cart.customer_id,
      })
    }

    const query = container.resolve("query")

    const { data: carts } = await query.graph({
      entity: "cart",
      filters: {
        id: cart.id,
      },
      fields: ["customer_id"],
    })

    return new StepResponse({
      customer_id: carts[0].customer_id,
    })
  }
)

In the example above, we add customer_id to the context along with the fixed properties is_return and enabled_in_store. This means that if a shipping option rule includes a customer_id attribute, the shipping option will be included in the result set.

The hook has been introduced in the following workflows:

  • listShippingOptionsForCartWithPricingWorkflow
  • listShippingOptionsForCartWorkflow

Please make sure to register the hook in both, since they are used for shipping option validation across workflows.

Features

Bugs

Documentation

Chores

  • chore(docs): Updated UI Reference (automated) by @github-actions[bot] in #13491
  • chore(docs): Update version in documentation (automated) by @github-actions[bot] in #13490
  • chore(js-sdk): add ignore tag to views by @shahednasser in #13503
  • chore: update TSDocs of JS SDK store methods to clarify required auth by @shahednasser in #13507
  • chore(core-flows): use directory convention for locking steps by @shahednasser in #13501
  • chore: add a link to the storefront docs for cart item totals type by @shahednasser in #13514
  • test(): test dynamic max workers and improve CI by @adrien2p in #13516
  • chore(): Shard unit tests jobs by @adrien2p in #13525
  • fix(core-flows): Lock cart on shipping + taxes update by @olivermrbl in #13535
  • chore: add tsdocs for latest changes by @shahednasser in #13539
  • fix(): Prevent promotion filtering to exceed psql limits by @adrien2p in #13540

Other Changes

  • fix(js-sdk): skip null values in query qs.stringify by @leobenzol in #13460
  • feat(dashboard): update display of tracking/label URLs on order details by @docloulou in #11613
  • fix(dashboard): added missing currencies by @tehaulp in #13214
  • test(): Try to reproduce automatic promotion adjustments being applied twice by @adrien2p in #13533

Full Changelog: v2.10.2...v2.10.3

v2.10.2

12 Sep 13:57

Choose a tag to compare

Highlights

Indexing of core entities

Medusa Index was built to improve the developer experience of filtering data across modules while improving performance, especially for large data sets.

With this release, all entities in the core Medusa modules are now ingestible into Medusa Index. This unlocks cross-module filtering for all core and custom entities. The ingestion happens automatically, provided a link is defined between the entities, as described in our documentation.

For example, filtering Products by Sales Channels used to require the following steps:

// 🔴 BEFORE

const query = container.resolve("query")

// Sales Channels to filter by
const salesChannels = ["sc_1234"]

// Query the Sales Channel <> Product link table for matching links
const { data: links } = await query.graph({
  entity: "product_sales_channel",
  fields: ["product_id"],
  filters: {
    sales_channel_id: salesChannels
  }
})

// Construct the products filter using the matched links
const productFilters = {
  id: links.map(link => link.product_id)
}

// Finally query the products with the filters
const { data: products } = await query.graph({
  entity: "product",
  fields: ["id"],
  filters: productFilters
})

With Index, the same example from above can be done with a single query:

// 🟢 NOW

const query = container.resolve("query")

// Sales Channels to filter by
const salesChannels = ["sc_1234"]

// Query the products with the sales channel filter
const { data: products } = await query.index({
  entity: "product",
  fields: ["id"],
  filters: { sales_channels: { id: salesChannels } },
})

Note that in the second code snippet, we use query.index instead of query.graph to query Medusa Index.

For setup and usage details of Medusa Index, see our documentation.

Locking all cart operations

All cart workflows in Medusa's core are now locking the cart during execution to eliminate the risk of concurrent changes.

We would recommend that you introduce the same locking mechanism to your own custom cart workflows.

Here's how you would do it:

export const myCustomAddToCartWorkflow = createWorkflow("my-custom-add-to-cart-workflow",
  (input) => {
    // Acquire a lock on the cart ID as the first step in the workflow
    acquireLockStep({
      key: input.cart_id,
      timeout: 2, // Attempt to acquire the lock for two seconds before timing out
      ttl: 10, // Lock is only held for a maximum of ten seconds
    })

   // Run all regular cart operations

    // Release the lock on the cart ID as the last step in the workflow
    releaseLockStep({
      key: cart.id,
    })

    return new WorkflowResponse({ ... })
  }
)

Features

  • feat(promotion): Allow buyget promotion to apply multiple times on cart by @riqwan in #13305
  • feat(admin): add view configuration client infrastructure by @srindom in #13186
  • Feat/datatable core enhancements by @srindom in #13193
  • feat(ui): add column visibility and drag-and-drop reordering support by @srindom in #13198
  • feat(admin): add configurable order views by @srindom in #13211
  • feat(dashboard,cart,types,utils): refine order details summary by @willbouch in #13313
  • chore(orchestration): add support for autoRetry, maxAwaitingRetries, retryStep by @adrien2p in #13391

Bugs

Documentation

Chores

  • chore(docs): Update version in documentation (automated) by @github-actions[bot] in #13340
  • chore(docs): Updat...
Read more

v2.10.1: Fixes regression in performance for cart operations

28 Aug 21:02

Choose a tag to compare

Highlights

We identified a performance regression affecting cart operations in the latest release. This issue resulted in slower response times for cart-related HTTP requests. The regression was caused by a change introduced to control concurrency in cart operations, which inadvertently impacted performance.

To address this issue, we have reverted the change, restoring cart operation performance to its previous state. To prevent similar issues in future releases, we are implementing regression tests as part of our release pipeline. These tests will help detect performance regressions early in the development process, ensuring a more stable and reliable experience.

We apologize for the inconvenience this may have caused for some applications.

Documentation

Chores

  • chore(docs): Update version in documentation (automated) by @github-actions[bot] in #13327
  • chore(docs): Generated DML JSON files (automated) by @github-actions[bot] in #13328
  • chore: add since tag to shipping option type events by @shahednasser in #13331
  • chore(core-flows): revert idempotent cart by @carlos-r-l-rodrigues in #13336

Full Changelog: v2.10.0...v2.10.1

v2.10.0: Draft Orders, Free shipping promotion, and Shipping Option Types

28 Aug 15:47

Choose a tag to compare

Highlights

This release contains breaking changes, so please read the release notes carefully. The breaking changes are all the result of fixing behavior that has not worked as intended until now.

Idempotency in our Workflow Engine

Warning

Breaking change

The idempotent configuration on workflows no longer retains executions in the database after completion, unless a retention time is specified. Up until now, if the idempotent flag was set, workflow executions were retained indefinitely, which was never the intention. The intention was to make a workflow idempotent for the duration of its execution, and only longer if the retention time was explicitly configured.

As part of this change, we are enabling the idempotent flag on all cart workflows to prevent concurrent mutations on cart operations.

Return type of methods in MedusaService

Warning

Breaking change

The return types of update and create in MedusaService have been corrected to properly handle the different shapes of inputs.

The behavior is now as follows:

createT({ ... }): T
createT([{ ... }]): T[]

updateT({ id: "br_1234", name: "Hermes"  }): T
updateT([{ id: "br_1234", name: "Hermes" }, { id: "br_4321", name: "Loewe"  }]): T[]
updateT({ selector: { country_of_origin: "France" }, data: { language: "French" }}): T[]

Compensating emitEventsStep

Warning

Breaking change

Previously, if the emitEventStep queued an event and the workflow later failed, the compensating flow would emit that queued event instead of removing it. This caused unexpected behavior during rollback.

With this change, when a workflow is compensated, all queued events emitted in the workflow are cleared. This was the intended behavior from the get-go, but the bug was only discovered recently.

If you want to keep the previous behavior, you can create your own version of the emitEventsStep without a compensation step.

Manage Shipping Option Types in Admin

Warning

Breaking change

The admin dashboard now supports managing shipping option types, making it easier to categorize options such as Standard and Express.

Key updates:

  • Settings: New "Shipping Option Types" page (accessible from "Locations & Shipping")
  • Create and update types with label, description, and code
  • Shipping option creation flow: Choose a type from a dropdown
  • Shipping option update flow: Change a type from a dropdown

This brings shipping option management to the dashboard, aligning it with existing backend support.

The introduction of Shipping Option Types comes with a migration, cleaning up old dummy types from the database. Up until now, we've created dummy types for Shipping Options as part of their creation flow. These types were placeholders, which is why we are now replacing them.

The migration will do the following:

  • Delete all Shipping Option Types with code type-code from the database
  • Disassociate these types from Shipping Options
  • Create a new Shipping Option Type named "Default" with code default
  • Associate the default type with Shipping Options

This migration will only affect Shipping Options that reference the type-code type. If you have been using your own types, they will remain as is. On the contrary, if you have been relying on type-code, this is a breaking change.

Draft Orders

The new Draft Order plugin adds flexible order management directly in Medusa Admin. Store admins can create, edit, and finalize orders through a dedicated UI, while developers gain a new primitive for building custom workflows.

Key features:

  • Create and manage draft orders from Medusa Admin
  • Support for custom pricing, line items, and shipping methods
  • Streamlined handling of B2B and complex sales

The Draft Order plugin is installed by default from version >=2.10.0. It is usable for version >=2.4.0 but requires explicit installation and registration.

Read more in our documentation.

Free shipping Promotions

This release introduces support for free shipping promotions available directly from the admin dashboard. You can apply free shipping to specific Shipping Option Types, e.g., "Standard", but not "Express".

Custom Logger Support

You can now replace the default logger with your own implementation by providing a class that follows the Logger interface.

Example:

// medusa-config.ts
import { MyCustomLogger } from "./custom-logger"

export default defineConfig({
  // ...
  logger: MyCustomLogger,
})

The logger should implement the Logger interface.

Shipping Option Tax Rates

You can now create tax rates specifically for shipping options within tax regions, similar to product-specific tax rates.

Metadata for Product Tags

The product tag domain now supports metadata, with a UI for creating and managing metadata values.

Features

Bugs

  • fix(utils): fix promotion case of each allocation not applying its total amount by @riqwan in #13199
  • fix(utils): auto generated update method return by @carlos-r-l-rodrigues in #13225
  • fix(dashboard): table inclusive date filter by @fPolic in #13053
  • fix(dashboard): create product selected inventory item display by @fPolic in #13111
  • fix(core-flows, dashboard, types): improve allocation flows on Admin by @fPolic in #12572
  • fix(dashboard): zero in float currency inputs by @fPolic in #13267
  • fix(dashboard): show fulfilment option on SO edit by @fPolic in #13269
  • fix(fulfillment): Geozone constraints builder by @olivermrbl in #13282
  • fix(): Cart operation should calculate item prices accounting for quantity by @adrien2p in #13251
  • fix(fulfillment): don't cascade shipping option delete to shipping option type by @fPolic in #13280
  • fix: Add created_at to workflow execution filtering typings by @sradevski in #13295
  • fix(dashboard): handle large resource count in tax rule override edit form by @fPolic in #13297
  • fix(medusa): fetching a product without a category with categories filed passed by @fPolic in #13020
  • fix(core-flows): pass backorder flag when recreating reservations after fulfilment cancelation by @fPolic in #13076
  • fix(core-flows): list order shipping options param type by @fPolic in #13316

Documentation

Read more

v2.9.0

14 Aug 12:47

Choose a tag to compare

Highlights

Throw error on invalid promotions

Warning

Breaking change

This release updates the promotion application logic to throw when applying an invalid promotion code. This change improves the experience for developers building storefronts by allowing them to handle invalid discount codes directly from the API response, rather than checking cart promotions after the API request.

The backend will respond with a 400 and a message like so:

The promotion code HELLOWORLD is invalid

Add support for logical operators on Index

This release introduces support for logical operators on Index queries, such as:

const { data: products } = await query.index({
  entity: "product",
  filters: {
    $and: [
      { status: "published" },
      {
        $or: [
          { brand: { name: { $ilike: "%adidas%" } } }
        ]
      }
    ]
  }
})

In addition to this, this release also fixes a regression related to indexing custom module entities.

Resolve issue with promotion calculations in a tax-inclusive context

This release resolves an issue with the calculation of adjustments in a tax-inclusive calculation context.

When calculating the applicableTotal (the maximum amount a promotion can apply to an item), it was incorrectly based on the line item’s subtotal (excluding tax), while the promotion value was based on the total (including tax), causing incorrect adjustment calculations.

Bugs

Documentation

Chores

Other Changes

  • fix(dashboard): correct overflow in a few settings edit forms by @SteelRazor47 in #11982
  • fix: createCustomerGroupsStep rollback, delete created customer groups… by @jbrigbyjs in #13056
  • This fixes the discount_ calculation logic and promotion tax inclusiveness calculation by @scherddel in #12960
  • feat: added pacific franc currency by @tehaulp in #13086
  • fix(types,utils,promotion): Move from total to original_total to resolve edge case for adjustments calculation by @scherddel in #13106
  • fix(utils):Fix on precision for high quantities for items when promotion is applied by @scherddel in #13131
  • Made in operator work as In insted of equal logic by @mikkel-lindstrom in #13078

New Contributors

...

Read more

v2.8.8: Improved error logging and overall robustness of core operations

24 Jul 08:08

Choose a tag to compare

Highlights

Carry over cart promotions to order

The cart-completion workflow has been updated to transfer promotions from carts to orders upon completion. This means you can now access promotions on the order like so:

const { data } = await query.graph({
  entity: "order",
  fields: ["id", "promotions.*"]
})

const order = data[0]

console.log(order.promotions)

// [{
//   "id": "promo_1234",
//   "code": "10OFF",
//   "is_automatic": false,
//   "is_tax_inclusive": false,
//   "type": "standard",
//   "status": "active",
//   "campaign_id": null,
//   "campaign": null,
// }]

Previously, promotions had to be accessed via the line item adjustments on the order.

Align accepted values in product import template

The CSV product import functionality has been updated to correctly handle unexpected columns. These columns are now ignored during import, ensuring a smoother process for creating and updating products.

Improved error logging

Client-side 4xx responses (such as 404s) are no longer logged as errors. These responses typically indicate expected conditions. Only 5xx responses, which indicate server-side issues, are now logged as errors. This reduces unnecessary noise in logs, making it easier to focus on actual server problems.

Expanded order methods in the JS SDK

Added archive and complete methods to the admin.order module of the JS SDK. These methods provide support for the POST /admin/orders/{id}/archive and POST /admin/orders/{id}/complete endpoints, giving developers full access to the order lifecycle directly from the SDK.

The methods are used like so:

await sdk.admin.order.archive("order_123");
await sdk.admin.order.complete("order_123");

Bugs

  • fix(link-modules,core-flows): Carry over cart promotions to order promotions by @riqwan in #12920
  • fix(core-flows): updating tax lines when draft order shipping is removed by @fPolic in #12919
  • fix(types): add attachments to CreateNotificationDTO type by @shahednasser in #12936
  • fix(dashboard): combobox multiitem clear by @fPolic in #12939
  • fix(core-flows): useQueryGraph util return type by @adrien2p in #12962
  • chore(workflow-engine-*): Align event subscribers management by @adrien2p in #12976
  • fix(orchestration): Prevent workf. cancellation to execute while rescheduling by @adrien2p in #12903
  • fix: accepted values in import with template by @juanzgc in #12969
  • fix(core-flows): properly delete variant inventory item by @fPolic in #12958
  • chore(link-modules): keep promotion alias by @riqwan in #12928
  • fix(wfe): should notify when finished + add state info by @adrien2p in #12982
  • fix(wfe): add missing state in inmemory notify on run finished by @adrien2p in #12987
  • fix(dashboard): allocation UI for orders with more than 20 reservation items by @fPolic in #12989
  • fix(pricing): fix pricing query when max_quantity is null by @riqwan in #12981
  • fix: fix script migrations order by @peterlgh7 in #13007
  • fix(workflows-sdk): fix step name config used before default by @carlos-r-l-rodrigues in #12926
  • fix(modules-sdk): Entity types by @olivermrbl in #13002
  • fix(dashboard, product): product attributes update with a relation update by @fPolic in #13019

Documentation

Chores

Other Changes

New Contributors

Read more