Skip to content

Releases: medusajs/medusa

v2.7.1: Strengthened workflow engine, expanded data for fulfillments, and more robust event handling

23 Apr 07:37
Compare
Choose a tag to compare

Highlights

Expanded fulfillment data

This release extends fulfillment capabilities by exposing more core order data to fulfillment providers–that is, data passed upon creating fulfillments in Medusa.

The additional data included on orders passed to providers are:

  • display_id
  • region
  • customer_id
  • customer
  • sales_channel_id
  • sales_channel
  • items.variant.upc
  • items.variant.sku
  • items.variant.barcode
  • items.variant.hs_code
  • items.variant.origin_country
  • items.variant.product.origin_country
  • items.variant.product.hs_code
  • items.variant.product.mid_code
  • items.variant.product.material
  • items.tax_lines.rate
  • subtotal
  • discount_total
  • tax_total
  • item_total
  • shipping_total
  • total
  • created_at

Strengthened workflow engine

We have improved the core internals of our workflow engine, including fixing an issue with events grouping not working correctly for async workflows.

Read more in the PRs below:

Features

  • feat(core-flows, js-sdk, medusa): draft order shipping removal by @fPolic in #12124
  • feat: Add support for dynamoDB for storing sessions and some types cleanup by @thetutlage in #12140
  • feat(orchestration): skip on permanent failure by @carlos-r-l-rodrigues in #12027
  • feat: Add support for uploading a file directly to the file provider from the client by @sradevski in #12224

Bugs

  • fix(dashboard): display null sku by @fPolic in #12155
  • fix(types): fix type of application_method_type filter by @shahednasser in #12160
  • fix(core-flows): draft order reservations by @fPolic in #12115
  • fix(index): Default schema typings by @adrien2p in #12183
  • fix(): Event group id propagation and event managements by @adrien2p in #12157
  • fix(payment): properly delete refund by @fPolic in #12193
  • fix: apply additional data validator using a global middleware by @thetutlage in #12194
  • fix(dashboard): properly register settings custom routes by @fPolic in #12198
  • fix(): Properly handle workflow as step now that events are fixed entirely by @adrien2p in #12196
  • fix(core-flows): export registerOrderDeliveryStep by @shahednasser in #12221
  • fix: expose beforeRefreshingPaymentCollection hook by @thetutlage in #12232
  • fix(medusa): sales_channel_id middleware manipulation leading to lost of the sc by @adrien2p in #12234
  • fix: steps to return undefined and still chain the config method by @thetutlage in #12255
  • fix(core-flows, link-modules): return fulfillment creation by @fPolic in #12227
  • fix: oas CLI to convert Windows paths to unix by @thetutlage in #12254

Documentation

Chores

Other Changes

New Contributors

Read more

v2.7.0: Save payment methods, improved links, and performance improvements

11 Apr 08:53
Compare
Choose a tag to compare

Highlights

Support multiple payment account holders for customers

Warning

Breaking changes

This release adds support for a customer to have multiple account holders, therefore supporting multiple payment providers at the same time.

If you were using customer.account_holder in your implementation, you must change it to customer.account_holders and choose the relevant one for the provider_id you want to use.

If you haven't done anything custom with payments and account holders, there are no breaking changes.

Enforce integrity constraints on module links

This release enforces integrity constraints on module links in the application layer.

The constraints are enforced as exemplified below:

One-to-one

export default defineLink(
  ProductModule.linkable.product,
  CustomModule.linkable.customModel
)

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

// This throws
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_321",
  },
})

// This throws
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_321",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

One-to-many / Many-to-one

export default defineLink(
  ProductModule.linkable.product,
  {
    linkable: CustomModule.linkable.customModel,
    isList: true,
  }
)

// export default defineLink(
//   { 
//     linkable: ProductModule.linkable.product,
//     isList: true,
//   },
//   CustomModule.linkable.customModel
// )

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_321",
  },
})

// This throws
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_456",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

Many-to-many

export default defineLink(
  {
    linkable: ProductModule.linkable.product,
    isList: true
  },
  {
    linkable: CustomModule.linkable.customModel,
    isList: true,
  }
)

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_123",
  },
  "customModule": {
    custom_id: "cus_321",
  },
})

// This succeeds
await link.create({
  [Modules.PRODUCT]: {
    product_id: "prod_456",
  },
  "customModule": {
    custom_id: "cus_123",
  },
})

Introduce dynamic pricing workflow hooks

This release introduces workflow hooks to override the default pricing context passed to the pricing module when calculating prices for variants and shipping options.

Prices with custom rules need a different context than what is passed by default. For example, say you have a custom rule that ties prices with a specific location location_id: loc_1234. To account for this rule in the price calculation logic, the pricing context needs to be populated with a location_id.

The pricing hooks are used as follows:

// src/workflows/hooks/pricing-hooks.ts
import { addToCartWorkflow } from "@medusajs/medusa/core-flows";
import { StepResponse } from "@medusajs/workflows-sdk";

addToCartWorkflow.hooks.setPricingContext(() => {
  return new StepResponse({
    location_id: "loca_1234", // Special price for in-store purchases
  });
});

Assuming you have a custom pricing rule { attribute: "location_id", operator: "eq", value: "loca_1234" }, this would be part of the pricing result set when retrieving the associated product variant or adding the same to the cart.

The hooks have been introduced to the following workflows:

  • addToCartWorkflow
  • createCartsWorkflow
  • listShippingOptionsForCartWorkflow
  • refreshCartItemsWorkflow
  • updateLineItemInCartWorkflow
  • addLineItemsToOrderWorkflow
  • updateClaimShippingMethodWorkflow
  • createOrderWorkflow
  • updateExchangeShippingMethodWorkflow
  • createOrderEditShippingMethodWorkflow
  • updateOrderEditShippingMethodWorkflow
  • createCompleteReturnWorkflow
  • updateReturnShippingMethodWorkflow

Please don't hesitate to submit a Github issue, if you want us to consider adding the hook to more workflows.

Resolve issues with broken plugin dependencies in development server

This release reworks how admin extensions are loaded from plugins and how extensions are managed internally in the dashboard project.

Previously we loaded extensions from plugins the same way we do for extensions found in a user's application. This being scanning the source code for possible extensions in .medusa/server/src/admin, and including any extensions that were discovered in the final virtual modules.

This was causing issues with how Vite optimizes dependencies and would lead to CJS/ESM issues.

To circumvent the above issue, we have changed the strategy for loading extensions from plugins. We now build plugins slightly different–if a plugin has admin extensions, we build those to .medusa/server/src/admin/index.mjs and .medusa/server/src/admin/index.js for a ESM and CJS build.

When determining how to load extensions from a source, we follow these rules:

  • If the source has a medusa-plugin-options.json or is the root application, we determine that it is a local extension source and load extensions as previously through a virtual module.
  • If it has neither of the above, but has a ./admin export in its package.json, then we determine that it is a package extension, and we update the entry point for the dashboard to import the package and pass its extensions along to the dashboard manager.

Changes required by plugin authors

The change has no breaking changes, but requires plugin authors to update the package.json of their plugins to also include a ./admin export. It should look like this:

{
  "name": "@medusajs/plugin",
  "version": "0.0.1",
  "description": "A starter for Medusa plugins.",
  "author": "Medusa (https://medusajs.com)",
  "license": "MIT",
  "files": [
    ".medusa/server"
  ],
  "exports": {
    "./package.json": "./package.json",
    "./workflows": "./.medusa/server/src/workflows/index.js",
    "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
    "./modules/*": "./.medusa/server/src/modules/*/index.js",
    "./providers/*": "./.medusa/server/src/providers/*/index.js",
    "./*": "./.medusa/server/src/*.js",
    "./admin": {
      "import": "./.medusa/server/src/admin/index.mjs",
      "require": "./.medusa/server/src/admin/index.js",
      "default": "./.medusa/server/src/admin/index.js"
    }
  },
}

Improve performance of price-selection

This release comes with significant optimizations of the pricing retrieval query to improve performance, especially on large datasets.

We have:

  • Removed costly subqueries and excessive joins
  • Leveraged targeted subqueries and indexed filters for rule checks
  • Optimized queries without rule contexts to bypass unnecessary logic

The improvements have been tested on a dataset with ~1,200,000 prices and ~1,200,000 price rules. The impact of the changes are as follows:

  • Before: ~718ms execution time
  • After: ~17ms execution time

We are dedicated to continuously improve performance, so you can expect further improvements going forward.

Improve the robustness of payment workflows

This release improves the robustness of payment workflows by squashing a range of minor bugs.

These fixes can be found in the following PRs:

Features

  • fix(medusa,utils,test-utils,types,framework,dashboard,admin-vite-plugin,admin-bundler): Fix broken plugin dependencies in development server by @kasperkristensen in #11720
  • refactor: use module name as the snapshot name by @thetutlage in #11802
  • feat: Change customer to account_holder to be one-to-many by @sradevski in #11803
  • feat: add check for uniqueness when creating links with isList=false by @thetutlage in #11767
  • fix(types,order,medusa): Create credit lines + hooks by @riqwan in #11569
  • feat(dashboard,js-sdk,admin-shared): add customer addresses + layout change by @riqwan in #11871
  • feat(core-flows,types,utils): make payment optional when cart balance is 0 by @riqwan in #11872
  • feat(medusa,types): add enabled plugins route by @riqwan in #11876
  • feat: Add support for generating a production-ready config out of the… by @sradevski in #11850
  • feat: add support for accessing step results via context by @thetutlage in https://github.com/medusajs/medusa/p...
Read more

v2.6.1: Improved Scheduled Jobs

10 Mar 15:47
Compare
Choose a tag to compare

Highlights

Scheduled Jobs execution

Ensure Scheduled Jobs are only executed by server instances running in worker or shared mode. Our workflow engine powers scheduled jobs, and we create a workflow for every job. Scheduled Jobs (i.e. workflows) are not registered in servers that run in server mode. Consequently, if a server instance tries to pick up a Scheduled Job, it will fail, as it cannot find the underlying workflow. This change ensures that only worker and shared server instances execute Scheduled Jobs.

Features

  • feat(workflows-sdk): Allow when then in parallelize by @adrien2p in #11756

Bugs

Documentation

Chores

Full Changelog: v2.6.0...v2.6.1

v2.6.0

04 Mar 13:22
Compare
Choose a tag to compare

Highlights

Authentication improvements

This release enhances security around resetting passwords.

Updated token handling in password resets

Warning

Breaking changes: this change is not backward compatible due to security considerations

The token for updating provider identity is now accepted as a Bearer token in the authorization header instead of query parameters.

Before:

POST /auth/user/emailpass/update?token=ey...

After:

POST /auth/user/emailpass/update
// headers
{ authorization: "Bearer ey..." }

This enhances security around token management, for example, by preventing token exposure in logs.

More specifically, this will affect the reset password flow that relies on this endpoint.

Required action

  • In your client (e.g. Next.js storefront), move the token from query params to the authorization header

Removed deprecated actorType from reset password event
We have removed a long deprecated property, actorType from the reset password event. Instead, use the actor_type.

API Routes loader refactor

Important

While these changes sound dramatic, if you have used middleware and API Routes as intended, there will be no required actions nor breaking changes.

This release includes a refactor of our API Routes loading mechanism. The previous loader had long-standing issues that required hacky workarounds. To resolve these issues, we’ve rebuilt the loader from scratch to ensure predictable behavior in how routes and middleware are registered within the application.

The root of the issues with the old implementation was mainly related to the following decisions:

  • Plugins, Medusa core, and the application used isolated router instances of Express. This means global middleware and error handlers are isolated, too. If you define an error handler in your app, it won't be able to handle errors from the core routes.

  • Middlewares defined inside the src/api/middlewares.ts file were always defined before the filesystem routes, and this led to unexpected behavior in many scenarios. For example, the middleware handler registered for route admin/payments/payment-providers will also run when a request comes in for /admin/payments/:id, even though there is a static route in place for the /admin/payments/payment-providers URI.

  • There was no clarity on the ordering in which routes and middleware were executed. With the new implementation, this has been fixed (more on ordering is discussed below).

  • Overriding an existing route defines multiple express handlers under the hood. It worked previously, because the route handlers never call next, hence the previous implementation was never executed. With the newer implementation, the route overrides from a Medusa application will be registered over the routes from the core or the plugins.

To resolve these issues, we’ve rebuilt the loader from scratch to ensure predictable behavior in how routes and middleware are registered within the application. Additionally, the old API Routes loader unintentionally allowed overriding core API routes and middleware. While this behavior remains for now, it will be removed in a future release. Please prepare your application based on the behavior described in the following sections.

Global middleware

A global middleware is defined using the defineMiddlewares function without specifying an HTTP method.

defineMiddlewares([
  {
    matcher: '/admin',
    middleware: [
      (req, res, next) => console.log("Hi from global middleware")
    ],
  }
])

The order of registering global middleware is as follows:

  1. Global middleware from the core
  2. Global middleware from plugins (in the order plugins are registered)
  3. Global middleware from the app

Route middleware

A route middleware is also defined using the defineMiddlewares method. However, they must always specify the HTTP method(s) they want to target.

defineMiddlewares([
  {
    matcher: '/admin/*',
    methods: ["GET", "POST"],
    middleware: [
      (req, res, next) => console.log("Hi from route middleware")
    ],
  }
])

The order of registering route middleware is as follows:

  1. Route middleware from the core
  2. Route middleware from plugins (in the order plugins are registered)
  3. Route middleware from the app

API Routes

An API Route is always attached to a method, decided by the exported const from the route file. API Routes are registered by scanning the filesystem.

Two types of API Routes are supported:

  1. Static segments: /admin/products/route.ts
  2. Dynamic segments: /admin/products/[id]/route.ts

Sorting routes and middleware

Before registering routes + middleware with Express, they are sorted as described below:

  1. Global middleware
  2. Route middleware
  3. API Routes

On top of this, each segment from the API Route matcher is sorted by its specificity:

  1. Wildcard segments, e.g. /admin/products/*
  2. Regex segments, e.g. /admin/(product-types|product-collections)
  3. Static segments, e.g. /admin/products
  4. Dynamic segments, e.g. /admin/products/:id

Important note about middleware
Middleware never overrides any existing middleware; they are added to the stack of existing middleware. For example, if you define a custom validation middleware on an existing route, then both the original and the custom validation middleware will run.

Performance improvements

This release contains general and, in some areas, significant performance improvements. Over the past weeks, we have focused on removing performance bottlenecks in Medusa. While many of the improvements impacted the entire application, the most significant ones are for cart operations.

Here's a summary of the changes and improvements:

Application-level changes

  • Eliminated unnecessary module calls in cart workflows to ensure we don't create unused transactions and have redundant database communication
  • Minimized redundant data fetching in cart workflows, ensuring that only the data that is needed for the operation is fetched
  • Changed workflows to accept prefetched data instead of always fetching the data independently
  • Introduced finer control over cart refresh logic to ensure we don't perform unnecessary cart refreshes
  • Replaced the Redis del operation with the non-blocking delete unlink to reduce the load on Redis
  • Replaced inefficient soft and hard deletes by performing the operation in a single query instead of multiple
  • (Dashboard) Minimized redundant data fetching for dashboard pages

Database-level changes
These changes are a result of thorough testing of our own infrastructure. Therefore, they should be deemed a recommendation rather than an absolute configuration. Additionally, it is important to highlight that these tests were run against a Neon Database with a PgBouncer setup. Without PgBouncer and Neon, the configuration would likely look different.

// medusa-config.ts
export default defineConfig({
  projectConfig: {
    // ...other options
    databaseDriverOptions: {
      pool: {
        max: 8, // Fixed pool size of 8
        idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
        connectionTimeoutMillis: 10000, // Fail on connection attempt after 10 seconds
        createRetryIntervalMillis: 100 // Time between connection retries
      },
    },
    ...
  },
}

Results

We have run two tests:

Test 1:

  • Operation: Add to cart
  • Database spec:
    • 0.25 CPU
    • 1 GB RAM
  • Server spec:
    • 1 CPU
    • 2 GB RAM
  • Load: 5 carts, operations for each cart run in parallel; for each cart, we add 100 items sequentially

p95: 1180ms -> 866ms
p50: 833ms -> 593ms

Test 2:

  • Operation: Add to cart
  • Database spec:
    • 0.5 CPU
    • 2 GB RAM
  • Server spec:
    • 1 CPU
    • 2 GB RAM
  • Load: 5 carts, operations for each cart run in parallel; for each cart, we add 100 items sequentially

p95: 1180ms -> 773ms
p50: 849 -> 537ms

Both tests show an approximate performance improvement of 30-35%.

Features

Bugs

  • fix(types, medusa): remove fulfillment and payment status filters from validator + http types by @shahednasser in #11604
  • fix(types): Allow providing either vite@5 or vite@6 for the peer dependency by @kasperkristensen in #11581
  • fix(promotion): scope uniqueness index to non deleted promotions by @riqwan in #11624
  • fix(core-flows): support 0 as a valid unit price for custom line items by @riqwan in #11631
  • fix(core-flows): unsafe access to a variant property by @fPolic in #11588
  • fix(medusa): deleting location level by @fPolic in #11630
  • fix: Exclude the health endpoint from being logged by @sradevski in ht...
Read more

v2.5.1

24 Feb 10:37
f00e6bf
Compare
Choose a tag to compare

Highlights

Support outlet routes, loader, and handle

This release introduces support for

  • exporting a loader from a route file
  • exporting a handle from a route file

Here's an example using the loader and handle:

// src/admin/routes/articles/[id]/page.tsx
import { Button, Container, Heading } from "@medusajs/ui";
import { Link, LoaderFunctionArgs, Outlet, UIMatch, useLoaderData } from "react-router-dom";

export async function loader({ params }: LoaderFunctionArgs) {
  const { id } = params;

  return {
    id,
  };
}

export const handle = {
  breadcrumb: (match: UIMatch<{ id: string }>) => {
    const { id } = match.params;
    return `#${id}`;
  },
};

const ProfilePage = () => {
  const { id } = useLoaderData() as Awaited<ReturnType<typeof loader>>;

  return (
    <div>
      <Container className="flex justify-between items-center">
        <Heading>Article {id}</Heading>
        <Button size="small" variant="secondary" asChild>
          <Link to="edit">Edit</Link>
        </Button>
      </Container>
      {/* This will be used for the next example of an Outlet route */}
      <Outlet />
    </div>
  );
};

export default ProfilePage;

In the above example we are passing data to the route from a loader, and defining a breadcrumb using the handle.

See more in #11305.

Expand options for instrumentation via registerOtel

This release expands the available options in our registerOtel function from our instrumentation tooling. More specifically, the function will allow all options from OpenTelemetry's NodeSDK, which will open up support for a broader range of instrumentation tools, including Sentry.

Here's an example of configuring Sentry:

import Sentry from '@sentry/node'
import otelApi from "@opentelemetry/api";
import { registerOtel } from "@medusajs/medusa"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc" 
import { SentrySpanProcessor, SentryPropagator } from "@sentry/opentelemetry-node"

Sentry.init({
  dsn: "<INSERT SENTRY DSN>",
  tracesSampleRate: 1.0,
  instrumenter: "otel",
});

otelApi.propagation.setGlobalPropagator(new SentryPropagator());

export function register() {
  registerOtel({
    serviceName: "medusa",
    spanProcessor: new SentrySpanProcessor(),
    traceExporter: new OTLPTraceExporter(),
    instrument: {
      http: true,
      workflows: true,
      query: true
    },
  })
}

See more in #11460.

Enrich and improve structural logging

This release improves structural logging by expanding the data with additional information, including request ID, request size, response size, request duration, and more.

Here's an example log line:

{"level":"http","client_ip":"10.18.85.250","request_id":"53123ef7-f8e9-4e85-8aea-3fecfc7d9ba0","http_version":"1.1","method":"POST","path":"/admin/products","status":200,"response_size":"10754","request_size":"1550","duration":1079.301,"referrer":"-","user_agent":"node","timestamp":"2025-02-24T08:36:17.748Z"}

See more in #11489.

Allow custom storage in JS-SDK

This release introduces support for a custom storage solution in the JS-SDK, instead of using the built-in memory, session, and local. This allows you to pass a custom implementation of the storage interface to be responsible for token management.

See more in #11467.

What's Changed

Features

Bugs

Documentation

Read more

v2.5.0

11 Feb 11:25
Compare
Choose a tag to compare

Highlights

Revamped Payment Provider interface

Warning

Breaking changes

The payment provider interface, originally designed for Medusa V1, has now been redesigned for V2–a long overdue update. These changes deliver a more unified and intuitive API.

We apologise in advance for the inconvenience it might cause for your payment provider implementations.

The following are breaking changes:

Payment Provider interface

export interface IPaymentProvider {
   ...
 
-  initiatePayment(
-    data: CreatePaymentProviderSession
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse>
+  initiatePayment(data: InitiatePaymentInput): Promise<InitiatePaymentOutput>
 
-  updatePayment(
-    context: UpdatePaymentProviderSession
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse>
+  updatePayment(data: UpdatePaymentInput): Promise<UpdatePaymentOutput>
 
-  deletePayment(
-    paymentSessionData: Record<string, unknown>
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
+  deletePayment(data: DeletePaymentInput): Promise<DeletePaymentOutput>
 
-  authorizePayment(
-    paymentSessionData: Record<string, unknown>,
-    context: Record<string, unknown>
-  ): Promise<PaymentProviderError | PaymentProviderAuthorizeResponse>
+  authorizePayment(data: AuthorizePaymentInput): Promise<AuthorizePaymentOutput>
 
-  capturePayment(
-    paymentSessionData: Record<string, unknown>
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
+  capturePayment(data: CapturePaymentInput): Promise<CapturePaymentOutput>
 
-  refundPayment(
-    paymentSessionData: Record<string, unknown>,
-    refundAmount: BigNumberInput
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
+  refundPayment(data: RefundPaymentInput): Promise<RefundPaymentOutput>
 
-  retrievePayment(
-    paymentSessionData: Record<string, unknown>
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
+  retrievePayment(data: RetrievePaymentInput): Promise<RetrievePaymentOutput>
 
-  cancelPayment(
-    paymentSessionData: Record<string, unknown>
-  ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
+  cancelPayment(data: CancelPaymentInput): Promise<CancelPaymentOutput>
+
+  createAccountHolder?(
+    data: CreateAccountHolderInput
+  ): Promise<CreateAccountHolderOutput>
+
+  deleteAccountHolder?(
+    data: DeleteAccountHolderInput
+  ): Promise<DeleteAccountHolderOutput>
 
-  listPaymentMethods?(
-    context: PaymentProviderContext
-  ): Promise<PaymentMethodResponse[]>
+  listPaymentMethods?(
+    data: ListPaymentMethodsInput
+  ): Promise<ListPaymentMethodsOutput>
 
-  savePaymentMethod?(
-    input: SavePaymentMethod
-  ): Promise<PaymentProviderError | SavePaymentMethodResponse>
+  savePaymentMethod?(
+    data: SavePaymentMethodInput
+  ): Promise<SavePaymentMethodOutput>
 
-  getPaymentStatus(
-    paymentSessionData: Record<string, unknown>
-  ): Promise<PaymentSessionStatus>
+  getPaymentStatus(data: GetPaymentStatusInput): Promise<GetPaymentStatusOutput>
}

The input and return types of the updated interface can be found in this file.

Additionally, we've updated the interface to require payment providers to throw errors instead of returning them to the upstream payment module service.

Create Payment Sessions endpoint

In addition to the interface changes, we've also removed the context field from the endpoint creating payment sessions:

`POST /store/payment-collections/:id/payment-sessions

This change addresses a security risk. The context accepted by the payment module now only accept known fields, limiting the surface area of what can be passed through it.

Account Holders
Fortunately, the revamp did not only introduce breaking changes. This release also introduces support for Account Holders in payment providers. In Stripe, for example, we will create a customer for every signed up user on your platform so you can track payments more easily within Stripe. These methods are optional for payment providers.

You can find all the changes in this pull request.

Overriding Shipping Option for Fulfillments

Note

Schemas changes

This release improves fulfillment capabilities, allowing merchants to override the shipping option originally selected during order creation. For example, a different shipping option can now be specified directly in the dashboard when creating a fulfillment.

Key changes:

  • Product <> Shipping Profile link

A link between Products and Shipping Profiles has been introduced, similar to the relationship in Medusa V1. Products now require a Shipping Profile upon creation. The create and update forms in the dashboard have been updated accordingly.

  • Override Shipping Option on Fulfillments

The fulfillment creation endpoint now accept an optional shipping_option_id. If passed, it will override the shipping option used when placing the order.

POST /admin/orders/:id/fulfillments

{ ..., shipping_option_id: "so_1234"}

This functionality is also available in the dashboard, allowing merchants to specify the override when creating a fulfillment.

  • Data migration script

Since products now require a shipping profile, this release includes a data migration script. The script will assign all products to the default shipping profile, if it exists. The default shipping profile is determined by looking for a shipping profile with default (case insensitive) in its name.

By default, data migration scripts run as part of the medusa db:migrate command.

Alternatively, you can run the data migration separately from the regular database migrations. To do this, first run the migration command with an argument skipping scripts:

medusa db:migrate --skip-scripts

Then, run the command again without the argument:

medusa db:migrate

Type-hinting for custom module services

This release improves type-hinting for custom module services. The input types of autogenerated create and update methods will provide an approximate type inferred from the related data model. Please note, in this first iteration, all properties will be marked as optional in the type, even though they might be required at the DB-level. We intend to further improve this to bring the type even closer to what the database expects.

Clearing workflow_executions exceeding their retention time

Note

Schemas changes

This release introduces a recurring job within the workflow engine to clear stale workflow executions from the database. The job runs every hour and deletes workflow executions that have exceeded their configured retention time.

Retention time is set in the workflow execution options. However, to simplify this operation, we’ve introduced a retention_time column in the workflow_execution table.

For existing workflow executions, the retention time will be automatically set if it was previously configured in the workflow execution options. The retention time is expected to be specified in seconds. If it was mistakenly configured in milliseconds, you may encounter a Postgres error when casting the retention time to an integer.

If you run into this issue, you can manually run the migration while specifying the correct retention time for your workflow executions.

Other noteworthy changes

Changes to medusa start

The medusa start command is intended to be used for production envionments. Therefore, the NODE_ENV now defaults to production instead of development.

New Divider component

The Divider component has been added to the UI library.

Added Metadata forms

Metadata forms have been added for the Product Collection and Order pages.

Features

Bugs

Read more

v2.4.0

27 Jan 09:02
956a50e
Compare
Choose a tag to compare

Highlights

Plugins

This release introduces Plugins. Plugins are customizations distributed as npm packages. A plugin can do everything a regular Medusa application can, including defining HTTP routes and workflows, registering subscribers, exporting custom modules, or defining links between modules.

Read our documentation on Plugins to learn more.

MikroORM V6 upgrade

This release marks the completion of the migration of all core modules to the Data Model API. As part of this effort, we’ve upgraded our MikroORM dependency from V5 to V6, as V5 has entered maintenance mode. You can read more about the upgrade here.

While this upgrade introduced breaking changes, none of them will impact user projects directly. The only required action is upgrading the MikroORM dependencies in your project.

Required actions

Upgrade the following dependencies in your package.json alongside your Medusa packages:

"dependencies": {
-  "@mikro-orm/core": "5.9.7",
-  "@mikro-orm/knex": "5.9.7",
-  "@mikro-orm/migrations": "5.9.7",
-  "@mikro-orm/postgresql": "5.9.7",
+  "@mikro-orm/core": "6.4.3",
+  "@mikro-orm/knex": "6.4.3",
+  "@mikro-orm/migrations": "6.4.3",
+  "@mikro-orm/postgresql": "6.4.3",
...
"devDependencies": {
-  "@mikro-orm/cli": "5.9.7",
+  "@mikro-orm/cli": "6.4.3",
...

Update your lock-file by installing the dependencies:

npm install // use your preferred package manager

DataTable component

This release adds an opinionated DataTable component to @medusajs/ui. The DataTable component is an opinionated implementation of our Table component and @tanstack/react-table. It simplifies the setup of tables with advanced features such as pagination, sorting, filtering, row selection, and commands.

Read more about the DataTable and learn how to set it up in our documentation.

New translations added to Admin

This release adds Macedonian, Romanian, Chinese, Persian, Czech, Arabic, and Mongolian to supported languages in Medusa Admin.

Change in behavior of one-to-one relation

This release updates the generated SQL of the one-to-one relation in our Data Model API. Until now, defining a one-to-one relation would generate a unique constraint. Unique constraints do not account for soft-deleted rows, so new rows with the same foreign key as a previously deleted row cannot be inserted. With this release, one-to-one relations will generate a unique index instead to ensure that the uniqueness is only enforced on non-deleted rows.

Features

Bugs

Documentation

Chores

Other Changes

New Contributors

Read more

v2.3.1

20 Jan 12:52
Compare
Choose a tag to compare

Highlights

Add missing dependency in @medusajs/framework

In @medusajs/framework uninstalled dependencies are directly imported and resolved through transitive dependencies. However, the glob package is not a transitive dependency of any production dependency and hence it fails running the server with NODE_ENV=production.

Bugs

Documentation

Chores

Other Changes

Full Changelog: v2.3.0...v2.3.1

v1.20.11

20 Jan 15:26
Compare
Choose a tag to compare

Fixes

Documentation

Chores

New Contributors

Full Changelog: v1.20.10...v1.20.11

v2.3.0

17 Jan 15:38
Compare
Choose a tag to compare

Highlights

Data migrations script

Some features might require data migrations to ensure new functionality works as expected after an upgrade. Typically, these migrations can be dealt with by database migrations within a module. However, this is not the case when the changes affect the data models in several modules. In those scenarios, the data in multiple modules need to be brought to the desired state.

To support this, we've introduced data migration scripts. They are very similar to regular scripts in the sense, that you have access to the dependency container, i.e. all modules. However, instead of running manually, data migration scripts are run automatically as part of running medusa db:migrate.

How it works

We scour Medusa projects, plugins, and the core for files in the src/migration-scripts and execute them. The files should export a default function. These scripts are only run once, provided they succeed.

The flow is as follows:

  • Find files
  • Ensure the script_migrations table exists–if not, create it
  • Identify pending migration scripts (scripts that have not been ran previously)
  • Acquire a lock on the table
  • Begin loop, for each script
    • Insert migration script in the database
    • Execute script
    • Mark script as finished
  • Release lock

This release focuses on putting the foundation in place for data migrations and does not introduce any scripts.

See #10960 for more.

Cart validation hooks

All Cart workflows have been updated to include a hook to perform validation before executing the operation.

For example, in the completeCartWorkflow you can register a hook like so:

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

completeCartWorkflow.hooks.validate({ cart } => {
  if (!cart.metadata.approved_at) {
    throw new Error("Cannot complete unapproved cart")
  }
})

Variant Inventory UX upgrade

Warning

Breaking changes

New bulk editors have been added to improve inventory management. One on the product details page, and another on the inventory item list page.

To enable these improvements, we have had to introduce breaking changes to the endpoint updating location levels in batches.

Before

POST /admin/inventory-items/:id/location-levels/batch
{ creates: [ ... ], deletes: [ ... ] }

After

POST /admin/inventory-items/:id/location-levels/batch
{ create: [ ... ], delete: [ ... ], update: [...] }

This brings the endpoint in line with all other batch endpoints.

See #10630 for more.

Payment module changes

Warning

Breaking changes

Data models in the Payment Module have been cleaned up, removing unused fields. This is a breaking change.

The removed fields are region_id from the Payment Collection entity and cart_id, order_id, and customer_id from the payment entity.

See #10987 for more.

Promotion statuses

The process of creating a promotion is sometimes a lengthy one that requires a campaign to be designed, promotion rules to be drafted, approvals to go through, and time to test it out.

This release introduces statuses on promotions to enable these workflows.

Promotions can now be either active, inactive, or draft. All existing promotions will be set to active whileas new promotion will start in a draft state, unless specified otherwise.

See #10950 for more.

Features

Bugs

  • fix(pricing): PriceLists of type Sale should not override lower prices by @kasperkristensen in #10882
  • fix: event-bus-redis processor execute event before subscriber are loaded by @pmc12thsuki in #10823
  • fix(pricing): add null conditions for deleted at during price calculations by @riqwan in #10896
  • fix(modules): Fix miss leading provider resolution error by @adrien2p in #10900
  • fix(types): add missing inventory_items to input of createProductsWorkflow by @shahednasser in #10892
  • fix(orchestration): avoid retry when finished by @carlos-r-l-rodrigues in #10913
  • fix(core-flows): return refunded when all captured payments have been refunded by @riqwan in #10923
  • fix(dashboard, core-flows): improvements to order page on canceled orders by @riqwan in #10888
  • fix(core-flows): missing variable of when condition by @carlos-r-l-rodrigues in #10958
  • fix: Include is default billing and shipping fields in the address re… by @sradevski in #10970
  • chore(dashboard,icons): Update icons and switch icon in CategoryTree by @kasperkristensen in #10961
  • fix(index): Add type casting to raw order by by @adrien2p in #10899
  • fix: Put routes loader error inline by @sradevski in #10912
  • fix(framework): Exclude .d.ts files from the glob search and fix insert query by @adrien2p in #10990
  • fix(dashboard): delete user messages by @fPolic in #11004
  • fix(core-flows,medusa): use deleteRefundReasonsWorkflow in delete /admin/refund-reasons/:id by @shahednasser in #11012

Documentation

Read more