All posts
Forward Development

Medusa 2.22 drops Node.js 20: how to plan the upgrade safely

Medusa v2.22.0 requires Node.js 22.22.0 or later. Here is how to check hosting, CI and Docker, order the upgrade, and keep a rollback ready.

  • Medusa.js
  • Migrations
  • Operations

A routine dependency bump fails before it gets anywhere. CI goes red on install, or the container builds and then crashes on boot, and the cause is a line in the release notes nobody read: Medusa v2.22.0 no longer supports Node.js 20. The new minimum is Node.js v22.22.0. Until your runtime moves, your store is frozen at v2.21.x and cut off from every later fix. That includes v2.22.1, which is already out.

The change itself is small. The risk is in how many places pin a Node version without anyone remembering they do. This is the order we work through it in.

The requirement is a specific minor version, not just "Node 22"

The release notes say Node.js 20 has reached end-of-life, and they give the check:

node --version  # must print v22.22.0 or higher

A Node 22 that prints v22.11.0 does not meet it. This trips people up because a lot of environments that look like they are on Node 22 are on an early 22.x:

  • A Docker base image pinned to an old patch, or a floating node:22 tag served from a stale build cache
  • A managed host whose "Node 22" option lags the upstream releases
  • A CI runner image with a preinstalled Node that your workflow uses without saying so
  • A developer laptop on whatever nvm installed last year

If your project is already on Node 22.22.0 or later, the notes say you are unaffected. Confirm it in every environment anyway. Don't take one laptop's word for it.

Find every place that decides your Node version

Before you change anything, write down each place that sets the runtime. In a typical Medusa project:

  1. package.json engines.node. Set it to >=22.22.0 so that installs on an older runtime warn or fail early.
  2. .nvmrc / .node-version / .tool-versions. These drive local tooling and some hosts.
  3. Dockerfile FROM lines, including every stage of a multi-stage build. A common mistake is a builder stage on 22 with a runtime stage still on 20.
  4. CI config, such as the node-version input on actions/setup-node, or the image used by your GitLab or Bitbucket pipeline. Look for build matrices that still include 20.
  5. Hosting runtime settings. These include PaaS dashboards, buildpack config and environment variables that select a Node version.
  6. Workers and side processes. If you run the Medusa worker as a separate service, it has its own image or runtime setting.
  7. The storefront. A Next.js storefront deployed on its own doesn't strictly have to match the backend. But if it shares a monorepo, lockfile or CI job with the backend, it will move with it.

Each of these can silently override the others. The one that wins in production is the one you need to verify.

Native dependencies need a rebuild, not a copy

Compiled addons are built against a specific Node ABI. Anything installed through node-gyp or shipped as a prebuilt binary is affected: image processing, hashing, database drivers and some plugins. A node_modules folder built on Node 20 will not load on Node 22.

In practice:

  • Do a clean install on the new runtime. Delete node_modules, keep the lockfile and run npm ci (or the pnpm/yarn equivalent). Never cache node_modules across a runtime change in CI. Key the cache on the Node version as well as the lockfile hash.
  • List your native packages with npm ls and look up each one's supported Node range. Old pinned versions of image libraries are the usual culprits.
  • Check third-party Medusa plugins too. A plugin that hasn't been touched in a year may declare its own engines field or depend on an old native module.

If a dependency has no Node 22 build, you now have a separate decision to make: replace it, fork it or vendor it. Make that decision before the runtime work, not halfway through it.

Upgrade the runtime first, then Medusa, in separate deploys

Do not move to Node 22 and bump Medusa in the same release. If something breaks, you won't know which change caused it, and your rollback has to undo both.

The order we recommend:

  1. Runtime only. Keep your current Medusa version and move every environment from step two to Node 22.22.0 or later. Ship it, then let it run through real traffic, scheduled jobs and at least one full order cycle: cart, payment, fulfillment, refund. The notes say projects already on Node 22 are unaffected, which suggests v2.21.x runs on 22, but check that on your own stack rather than assuming it.
  2. Medusa packages. Once the runtime is stable, bump all @medusajs/* packages together to the latest 2.22 patch. Today that is v2.22.1. The material here doesn't say what v2.22.1 changes, so read its notes before you pin it. Mixing package versions across the Medusa monorepo causes more trouble than any single release does.

Test on a staging branch that mirrors production

Staging should run the same image, the same Node binary and a recent copy of the production database. A staging box with a different runtime tells you nothing.

Work through this list on the 2.22 branch:

  • Run database migrations against the production copy. v2.22.0 fixes a bug where a failed migration script exited with code 0. Now a failure actually fails. That is good news, but a deploy pipeline that was quietly passing before may now stop, so find out on staging.
  • Run the payment flows end to end, especially if you use Stripe. This release changes how deleting a payment session behaves when the PaymentIntent has already succeeded. It also makes refunds safe to retry with an idempotency key.
  • Check tracing dashboards. If OpenTelemetry instrumentation is on, query parameters now go in url.query instead of http.url. Only fields, expand, limit, offset, order and with_deleted are shown in clear text; everything else appears as REDACTED. Saved queries and alerts that parse http.url will break. Set http.allowedQueryParams deliberately rather than reaching for "*".
  • Check custom auth. The email/password provider gains a requireActorAssociation option, and provider identity matching has changed. If you have custom actor types, test logins for each one.
  • Check custom workflows. There are fixes to compensation behavior and to transform results that have a key property. Run any workflows you have written.

Keep a rollback you have actually rehearsed

A rollback plan you haven't tried is only a guess. Before the production deploy:

  • Tag the last known-good image for each step, the Node 22 runtime image and the 2.21.x application image, so you can redeploy by tag without rebuilding.
  • Snapshot the database right before running migrations. Rolling back the code doesn't roll back the schema. If 2.22 migrations have already run, going back to 2.21 means restoring that snapshot, and any orders placed in between must be reconciled by hand. Keep that window short.
  • Keep the steps separate. Because the runtime upgrade shipped on its own, a bad Medusa release rolls back to 2.21.x on Node 22, not to Node 20. You should never need to go back to an end-of-life runtime.
  • Write down the commands in your deploy repo and run them once on staging.

What to do this week

Run node --version in every environment listed above and write down the results. Wherever it prints less than v22.22.0, that is your work list. Ship the runtime change by itself, and let it run before you touch the Medusa packages.

If you are evaluating Medusa as the destination for a move off Magento, set Node 22.22.0 as the baseline from day one. See how we approach Magento 2 and Adobe Commerce to Medusa or Vendure migrations. If you want a second pair of eyes on the upgrade plan or the rollback, get in touch.

Need this done on a real stack?

Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.

Contact us