All posts
Forward Development

Upgrading to Vendure 3.8: NestJS 12 and Apollo 5 for plugins

Vendure 3.8 adds MCP, secret fields and a security fix. NestJS 12 and Apollo Server 5 are the real work. A plugin audit and staging plan.

  • Vendure
  • Migrations
  • Operations

You run a Vendure 3.7 store with several custom plugins. Most of them were written for one integration, then patched and left alone because they kept working. Vendure 3.8.0 is out, and it fixes a high-severity session advisory, so you want it soon. The release notes call it a minor release. The feature list covers AI agent access over MCP, encrypted secret fields, more capable custom fields and a refreshed Dashboard. Underneath those features, 3.8 moves to NestJS 12, @nestjs/graphql 14 and Apollo Server 5. Your custom code sits on that stack, so that is where the upgrade can break.

3.8 is a minor release that touches the framework underneath

Start with the reason to upgrade at all. 3.8.0 fixes GHSA-5fpx-9m35-w745, rated High. An authenticated session was not bound to the API it was created for. If a user was also an administrator, their Shop API session could be replayed against the Admin API. The only release that fixes it is 3.8.0. The 3.7.x patches up to 3.7.4 do not. 3.8.0 also hardens the earlier fix for GHSA-32jm-mf7r-7qw5, which was fixed in 3.7.3, and it includes every security fix from the 3.7.x patches. If you are holding at 3.7.4 because it is a patch release and feels safer, you are still exposed to the session-binding issue.

The framework bump is what makes this more than a version number change. Vendure's guidance is short: align any direct dependencies on @nestjs/* or @apollo/server with the new versions. A store with no custom server code may need nothing more. A store with plugins that import Nest or Apollo directly needs an audit before the upgrade.

Audit every plugin that imports Nest or Apollo directly

Run this before you change a version number:

grep -rnE "from '(@nestjs|@apollo)/" src/ plugins/
npm ls @nestjs/core @nestjs/graphql @apollo/server

The first command shows which of your files depend on the framework directly, not only through @vendure/core. The second shows duplicate copies. A plugin that pins its own @nestjs/common or @apollo/server can leave two copies in the tree. That tends to fail at bootstrap with dependency injection errors, and the error message rarely names the duplicate.

Sort what you find into three groups:

  • Resolvers and schema extensions. These are code using @Resolver, @Query, @Mutation, @ResolveField and the shopApiExtensions / adminApiExtensions schema strings. This is the bulk of most custom Vendure code. It usually survives a framework bump unchanged, but it has to be exercised under the new versions, not assumed to work.
  • Anything that touches Apollo itself. That includes custom Apollo plugins, error formatting, context construction and direct imports from @apollo/server. This is the group most likely to need changes. Read the Apollo Server 5 migration notes against each file. Do not rely on a summary.
  • Third-party plugins. Check whether each one has a release that declares compatibility with 3.8. If a plugin is unmaintained, you now own it.

Check tooling as well. GraphQL Playground is replaced by GraphiQL, and adminApiDebug and shopApiDebug no longer have any effect. If your debugging habits or internal docs depend on either, update them.

Look for plugin code that depends on behaviour 3.8 changes

Several behaviour changes in 3.8 will not stop your build but can break plugin logic.

ctx.session.token is now typed string | undefined and is always undefined on job queue workers. Under strict TypeScript, the type change shows up when you compile. Code that reads the token at runtime and assumes it exists will not show up until a job runs. A serialized RequestContext is now built from an explicit allowlist, so job data no longer carries the session token or request headers. Search for session.token and for anything in a job handler that reads headers from the context.

Checkout now recalculates the order when it moves to ArrangingPayment. It refuses the transition if the selected shipping method is no longer eligible. If you have a custom ShippingEligibilityChecker, it now runs at a point where it used to be skipped. Your storefront needs to handle that refusal with a clear message, not a generic error.

The Dashboard now sends only changed fields when updating Products, ProductVariants and Collections. Some plugins listen for those update events or validate inputs and assume the full entity arrives every time. Those plugins will see partial inputs.

Admin API sessions are now bound to the Admin API, so every administrator has to log in again once after the upgrade. Tell them in advance so nobody files a support ticket about being logged out.

Plan the data migrations before merging

There are two database steps, and one of them depends on timing.

The schema migration adds a unique constraint on every translation table. Generated migrations remove existing duplicates automatically. Back up first, and look at which rows get removed before you trust it, especially if you have imported catalogue data.

The order line promotion data migration is the step to get right the first time. If you have partially cancelled order lines from before 3.8, rescaleOrderLinePromotionAdjustments must run in the same deployment as the upgrade. The release notes say it cannot be added later. Query for partially cancelled lines before you plan the deployment, not afterwards.

Run staging as a full rehearsal of the deployment

Restore a recent production snapshot to staging. Upgrade every @vendure/* package to 3.8.0 together, then:

  1. Generate and run the migration against the snapshot. Diff the duplicate-translation cleanup.
  2. Run the promotion data migration if you need it, in the same deployment step you will use in production.
  3. Boot both the server and the worker. Many bugs show up only in the worker.
  4. Call every custom query and mutation through GraphiQL, on both APIs.
  5. Place orders through to payment. Include one where the shipping method becomes ineligible partway through checkout.
  6. Trigger every custom job and confirm none of them reads the session token.
  7. Edit a product in the Dashboard and confirm your update listeners still fire correctly.

If staging passes all seven, production should be a routine deployment.

Adopt secret fields now and pilot MCP before relying on it

Use now. Secret fields (secret: true) encrypt custom field and configurable-operation values at rest and redact them in API responses. If you currently keep API keys in plain custom fields, move them. Indexed custom fields and relation IDs exposed in GraphQL are also small, low-risk improvements.

Pilot. @vendure/mcp-plugin ships 42 tools, 18 on the shop toolset and 24 on the admin toolset, and @vendure/mcp-sdk lets you add your own with @McpTool. It is useful for practical automation such as order lookups or catalogue queries. Enable the shop toolset in staging first. Grant admin tools one at a time, and only for tasks where you would also accept a script doing the work.

Wait. TypeORM v1 is opt-in, and projects stay on v0.3 by default. The override forces every copy of TypeORM onto v1, third-party plugins included. A plugin that relies on v0.3 APIs fails at runtime, not at install. Upgrade to 3.8 first, then make TypeORM v1 a separate change.

What to do this week

Run the grep above and count how many files import Nest or Apollo directly. Query for partially cancelled order lines. Those two answers tell you whether 3.8 is an afternoon's work or a planned sprint. If you are moving onto Vendure from Magento, that inventory belongs in the Magento 2 to Medusa or Vendure migration plan too, so the new store starts on 3.8 and you don't rewrite plugins twice. If you want a second engineer to go through your plugin audit, 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