graphql revert, security hardening, dashboard fixes, perf wins
Vendure
Developer ProductivityHeadless TypeScript commerce backend built on NestJS with a GraphQL API and plugin‑first architecture.
Features
- Plugin architecture enabling extensibility without core forks
- TypeScript, Node.js, NestJS stack with strong typing end‑to‑end
- Single GraphQL API serving any frontend or sales channel
- Production‑tested commerce building blocks (catalog, orders, customers, etc.)
Recent releases
View all 9 releases →- Romanian translations added to the dashboard
- Bulk cancel action with human‑readable duration strings
Full changelog
Highlights
This patch release lands a substantial round of stability, concurrency and dashboard fixes. The headline items address race conditions in promotion and coupon handling, prevent out-of-memory issues on large channel assignments, and harden the scheduled-task and state-machine subsystems against duplicated or partially-applied work. Thanks as ever to everyone who reported issues and contributed PRs.
Concurrency and integrity fixes
This release closes several long-standing edge cases that could cause data integrity issues under load or concurrent traffic:
- Coupon usage limits enforced under concurrent checkout (#4660) -- A race condition allowed customers to bypass the per-coupon usage limit when multiple checkouts completed concurrently. The check is now serialised correctly so the configured limit is always respected.
- Auto-applied promotion usage limits enforced (#4405) -- Promotions that auto-apply (no coupon code required) were not honouring their configured usage limits. They now respect the same limits as code-applied promotions.
- Atomic state-machine transitions on hook failure (#4689) -- If a state-transition hook threw after the entity state had been written, the entity could be left in an inconsistent state with hooks partially applied. Transitions are now atomic — a failing hook reverts the state change cleanly.
- No duplicate execution of fast scheduled tasks (#4681) -- Scheduled tasks running on a short interval could be picked up and executed by multiple workers in the same window. The scheduler now correctly deduplicates these executions.
Performance and scalability
- Avoid OOM on large product-to-channel operations (#4669) --
assignProductsToChannelandremoveProductsFromChannelnow use a query relation strategy that no longer hydrates the full product graph into memory. This unblocks customers who hit out-of-memory crashes when assigning thousands of products to a channel. - Job queues now created in
onModuleInit(#4680) -- Moving job-queue creation fromonApplicationBootstraptoonModuleInitmeans queues exist before any other module's bootstrap code runs, eliminating a class of "queue not found" startup races.
Core fixes
- Asset update with custom field relations (#4696) -- Updating an Asset that had a custom-field relation defined no longer fails. This pairs with the corresponding dashboard fix (#4695).
- Entity hydrator handles undefined relations (#4672) -- Hydrating an entity where a requested relation was undefined no longer throws.
- Customer user resolution via relation (#4468) -- The
Customer.userresolver now uses the configured relation instead of doing a separate email-based lookup, fixing edge cases where the email had been changed on the user record. - Admin UI handles
tokenMethodarray form (#4663, fixes #4656) -- WhentokenMethodis configured as an array (['cookie', 'bearer']), the generatedui-confignow serialises it correctly instead of producing an invalid value.
Dashboard fixes and improvements
- Asset save with custom fields (#4695) -- Saving an Asset with custom fields defined now works correctly from the dashboard.
- Action bar positioning relative to extensions (#4676) -- Extensions can now position action bar items relative to other extensions rather than only to built-in items, giving extension authors more control over toolbar layout.
- Graceful fallback on denied replace-extension (#4694) -- When a permission check denies a
replace-style extension, the dashboard now falls back to the original block instead of rendering nothing. - Custom-page permission checks (#4679) -- Custom pages now respect their declared
requiredPermissionsand won't render for users who lack them. isFullWidthmetadata prop implemented (#4638) -- PageBlocks can now opt into a full-width layout via metadata, useful for components like rich text editors.- Direct
@base-ui/reactimports dropped (#4697) -- Dashboard internals no longer import directly from@base-ui/react, going through the wrapped component layer instead. This keeps the public surface area consistent for extension authors. - Order modification preview includes nested fragments (#4640) -- Modification previews now include all required nested fields, so the preview matches what the modification will actually produce.
- Draft order mutation error messages (#4381) --
updateOrder*mutations on draft orders now surface their error messages to the UI rather than failing silently. - Promotions list default sort (#4688) -- The promotions list now opens with a sensible default sort instead of arbitrary order.
- Chart widget dynamic Y-axis width (#4516) -- The dashboard chart widget now sizes its Y-axis dynamically so long labels are no longer clipped.
- Empty
customFieldsselection handled (#4652) -- Custom-field components with no selection no longer throw. - Fulfillment arg defaults are strings (#4658) -- Fulfillment handler argument default values are now correctly stringified.
- Tanstack router generator stability (#4666) -- Inlining the route literal sidesteps a tanstack router-generator quirk that could produce broken route trees in certain layouts.
New dashboard features
- Bulk cancel action with human-readable durations (#4361) -- Order lists now expose a bulk-cancel action, and durations are rendered as human-readable strings (e.g. "3 days ago") rather than raw timestamps.
- Romanian translations (#4598) -- Romanian (
ro) is now supported in the dashboard.
i18n improvements
- Italian translations updated (#4645) -- A round of missing Italian (
it) strings has been added. - Swedish corrections (#4684) -- Several mistranslated Swedish (
sv.po) strings have been corrected. - Wrong-language msgstrs repaired (#4685) -- A bulk fix across
hr,nb,tr,it,ja,ko,heandroremoves msgstr entries that had drifted into the wrong language. Thei18n:applyscript has been hardened to prevent recurrences.
Plugins
BullMQJobQueuePluginfiltering (#4523) -- Job filtering in the BullMQ plugin now produces correct results when combining multiple filter fields.
What's Changed
- fix(ci): Remove [skip ci] from generate_docs workflow by @oliverstreissi in https://github.com/vendurehq/vendure/pull/4646
- fix(ci): Add gate job to unblock non-package PRs by @michaelbromley in https://github.com/vendurehq/vendure/pull/4649
- fix(docs): Fix broken links and outdated type names in custom form components docs by @gabriellbui in https://github.com/vendurehq/vendure/pull/4648
- docs: Fix typo in navigation-menu arrayToTree code snippet by @gabriellbui in https://github.com/vendurehq/vendure/pull/4593
- fix(core): Enforce usage limits for auto-applied promotions by @HouseinIsProgramming in https://github.com/vendurehq/vendure/pull/4405
- fix: Add missing include nested fragments in order modification preview by @Ryrahul in https://github.com/vendurehq/vendure/pull/4640
- fix: Add dynamic y axis width by @Ryrahul in https://github.com/vendurehq/vendure/pull/4516
- feat(dashboard): Add Romanian translations by @alingabrieldm in https://github.com/vendurehq/vendure/pull/4598
- fix: Add recursive flatten job filter for bull mq when filter is sent… by @Ryrahul in https://github.com/vendurehq/vendure/pull/4523
- fix: Make tab view scrollable by @Ryrahul in https://github.com/vendurehq/vendure/pull/4644
- feat(dashboard): Add bulk cancel action and human-readable duration t… by @Ryrahul in https://github.com/vendurehq/vendure/pull/4361
- chore: Bump @vendure-io/docs-generator to 0.1.1 by @michaelbromley in https://github.com/vendurehq/vendure/pull/4664
- fix(admin-ui): Handle tokenMethod array form when generating ui-config (#4656) by @Draykee in https://github.com/vendurehq/vendure/pull/4663
- fix(dashboard): Inline route literal for tanstack router-generator by @michaelbromley in https://github.com/vendurehq/vendure/pull/4666
- fix(core): Avoid OOM in product-to-channel assign/remove via query relation strategy by @arthur-nesterenko in https://github.com/vendurehq/vendure/pull/4669
- chore: Migrate package management from npm to Bun by @michaelbromley in https://github.com/vendurehq/vendure/pull/4675
- fix(core): Create job queues in onModuleInit instead of onApplicationBootstrap by @michaelbromley in https://github.com/vendurehq/vendure/pull/4680
- fix(core): Prevent coupon usage limit bypass via concurrent checkout race condition by @grolmus in https://github.com/vendurehq/vendure/pull/4660
- fix(core): Resolve customer user via relation instead of email lookup by @grolmus in https://github.com/vendurehq/vendure/pull/4468
- fix(dashboard): Support action bar positioning relative to extensions by @izumi0uu in https://github.com/vendurehq/vendure/pull/4676
- chore: Add local asset storage strategy and its factory to exports by @DanielBiegler in https://github.com/vendurehq/vendure/pull/4671
- fix(core): Handle undefined relation in entity hydrator by @izumi0uu in https://github.com/vendurehq/vendure/pull/4672
- fix(dashboard): Handle empty customFields selection when all fields h… by @Ryrahul in https://github.com/vendurehq/vendure/pull/4652
- fix(dashboard): Add missing Italian translations by @claudiolor in https://github.com/vendurehq/vendure/pull/4645
- feat(dashboard): implement
isFullWidthmetadata prop by @casperiv0 in https://github.com/vendurehq/vendure/pull/4638 - fix(dashboard): Repair wrong-language msgstrs across hr/nb/tr/it/ja/ko/he/ro and harden i18n:apply by @michaelbromley in https://github.com/vendurehq/vendure/pull/4685
- fix(dashboard): correct mistranslated Swedish strings in sv.po by @comega-johan in https://github.com/vendurehq/vendure/pull/4684
- fix(dashboard): Ensure fulfillment arg default value is a string by @kyunal in https://github.com/vendurehq/vendure/pull/4658
- chore(dashboard): Add error messages to update draft mutations by @LucidityDesign in https://github.com/vendurehq/vendure/pull/4381
- fix(core): Make state-machine transitions atomic on hook failure by @michaelbromley in https://github.com/vendurehq/vendure/pull/4689
- fix(dashboard): set default sort on promotions list by @casperiv0 in https://github.com/vendurehq/vendure/pull/4688
- fix(core): Prevent duplicate execution of fast scheduled tasks by @BibiSebi in https://github.com/vendurehq/vendure/pull/4681
- fix(dashboard): Check required permissions when rendering custom page… by @LucidityDesign in https://github.com/vendurehq/vendure/pull/4679
- fix(dashboard): Fall back to original on denied replace extension by @michaelbromley in https://github.com/vendurehq/vendure/pull/4694
- fix(dashboard): Align asset mutation selection set with other detail pages by @michaelbromley in https://github.com/vendurehq/vendure/pull/4695
- fix(core): Save Asset before running custom-field relation updates by @michaelbromley in https://github.com/vendurehq/vendure/pull/4696
- chore(dashboard): Drop direct @base-ui/react imports by @michaelbromley in https://github.com/vendurehq/vendure/pull/4697
New Contributors
- @arthur-nesterenko made their first contribution in https://github.com/vendurehq/vendure/pull/4669
- @izumi0uu made their first contribution in https://github.com/vendurehq/vendure/pull/4676
- @claudiolor made their first contribution in https://github.com/vendurehq/vendure/pull/4645
- @comega-johan made their first contribution in https://github.com/vendurehq/vendure/pull/4684
Full Changelog: https://github.com/vendurehq/vendure/compare/v3.6.2...v3.6.3
- Update to v3.6.2 (or respective patched versions v3.5.7, v2.3.4) as soon as possible.
- Patches are available for older supported branches: v3.5.x → v3.5.7, v2.3.x → v2.3.4.
- GHSA-9pp3-53p2-ww9v — Fixed SQL injection via languageCode query parameter and sanitized search terms for Postgres tsquery syntax
Full changelog
This is a high-priority security patch that addresses a vulnerability that was reported a few days ago, described in https://github.com/vendurehq/vendure/security/advisories/GHSA-9pp3-53p2-ww9v
You should update your Vendure version as soon as possible. Due to the severity of this vulnerability, we have also published patches for older versions for those who cannot yet update to the latest v3.6.x version:
- v3.6.x -> v3.6.2
- v3.5.x -> v3.5.7
- v2.3.x -> v2.3.4
Thank you to @jacobfrantz1 for responsibly disclosing this issue.
What's Changed
- core Fix SQL injection via languageCode query parameter (3ff0bc1)
- core Sanitize search term for Postgres tsquery syntax (32c947d)
- fix: Use shipping line tax instead of channel tax by @Ryrahul in https://github.com/vendurehq/vendure/pull/4624
Full Changelog: https://github.com/vendurehq/vendure/compare/v3.6.1...v3.6.2
- Allow creating single‑variant products without requiring option groups
- Dashboard widgets can specify `requiresPermissions` (e.g., gating order widgets behind `ReadOrder`)
- Support Zod v4 and re‑export Zod from `@vendure/dashboard`
Full changelog
Highlights
This patch release addresses some important issues that were flagged by our community after the recent v3.6.0 release. Thank you to everyone who contributed issue reports and PRs with fixes!
Product creation flow improvements
This release includes several fixes and improvements to the product creation experience in the dashboard:
- Single-variant products no longer require option groups (#4616) -- You can now create a product with a single variant directly, without needing to set up option groups first. This simplifies the workflow for products that don't have multiple options (e.g. sizes, colors).
- Validation no longer blocks unchecked variant rows (#4610) -- Previously, unchecked (excluded) variant rows in the creation form could still trigger validation errors, preventing you from saving. These rows are now correctly skipped during validation.
- Fixed option group edit link on variant detail page (#4620) -- The link to edit an option group from the variant detail page was broken; this is now corrected.
CLI codemod fix
- tsconfig resolution in the migration codemod (#4599) -- The
@vendure/clicodemod for migrating dashboard extensions now correctly resolvestsconfig.jsonby walking up from the target directory rather than only looking in the current working directory. This fixes failures when running the codemod from a different directory than where the tsconfig lives.
Core fixes
- Product channel assignment (#4618) --
assignProductsToChannelwas not correctly assigning the Product entity itself to the target channel, only its variants. This is now fixed. - Order splitting channel deduplication (#4632) -- When an order's channel matched the default channel, the
OrderSplittercould produce duplicate channel entries, causing issues downstream. Channels are now correctly deduplicated.
Dashboard fixes and improvements
- Toaster z-index stacking (#4634) -- Toast notifications are now portalled to
document.body, fixing an issue where they were hidden behind dialogs due to CSS stacking context isolation. - Address dialog scrollability (#4622) -- The address dialog is now scrollable with a max height, fixing overflow issues on smaller screens.
- Query key invalidation (#4630) -- Fixed a cache invalidation mismatch that could cause stale data after mutations.
- Zod v4 support (#4607) -- The dashboard now supports Zod v4 and re-exports Zod from
@vendure/dashboard, so extensions don't need to manage their own Zod dependency. - Dashboard widget permissions (#4627) -- Dashboard widgets can now specify
requiresPermissions, restoring a capability from the legacy Angular admin UI. The built-in order widgets are now gated behindReadOrder.
What's Changed
- ci: Fix Playwright install in publish_and_install workflow by @michaelbromley in https://github.com/vendurehq/vendure/pull/4603
- ci: Use cd instead of working-directory for Playwright install by @michaelbromley in https://github.com/vendurehq/vendure/pull/4605
- feat(dashboard): Support Zod v4 and re-export Zod from @vendure/dashboard by @dlhck in https://github.com/vendurehq/vendure/pull/4607
- fix(ci): Remove [skip ci] from docs manifest commit to unblock PR checks by @dlhck in https://github.com/vendurehq/vendure/pull/4611
- fix(dashboard): Use index-based identification for collection filters by @grolmus in https://github.com/vendurehq/vendure/pull/4428
- fix(dashboard): Skip validation for unchecked variant rows by @michaelbromley in https://github.com/vendurehq/vendure/pull/4610
- chore(dashboard): Sync i18n catalogs by @michaelbromley in https://github.com/vendurehq/vendure/pull/4613
- fix(cli): Resolve tsconfig by walking up from target directory by @michaelbromley in https://github.com/vendurehq/vendure/pull/4599
- fix(dashboard): Fix collection filter e2e test selector by @michaelbromley in https://github.com/vendurehq/vendure/pull/4615
- feat(dashboard): Allow creating single variant without option groups by @michaelbromley in https://github.com/vendurehq/vendure/pull/4616
- fix(ci): Tolerate i18n line reference changes in sync check by @michaelbromley in https://github.com/vendurehq/vendure/pull/4617
- fix(core): Assign Product entity to channel in assignProductsToChannel by @michaelbromley in https://github.com/vendurehq/vendure/pull/4618
- fix(dashboard): Fix option group edit link on variant detail page by @michaelbromley in https://github.com/vendurehq/vendure/pull/4620
- refactor: Replace local docs generation code with @vendure-io/docs-generator by @oliverstreissi in https://github.com/vendurehq/vendure/pull/4626
- fix: Make address dialog scrollabe with max h by @Ryrahul in https://github.com/vendurehq/vendure/pull/4622
- fix: Match query key invalidation by @Ryrahul in https://github.com/vendurehq/vendure/pull/4630
- fix(core): Deduplicate channels in OrderSplitter when channelId matches default (#4631) by @Draykee in https://github.com/vendurehq/vendure/pull/4632
- fix: Add creator portal in toaster to avoid isolation:isolate stacking by @Ryrahul in https://github.com/vendurehq/vendure/pull/4634
- feat(dashboard): Add requiresPermissions support to dashboard widgets by @niko91i in https://github.com/vendurehq/vendure/pull/4627
Full Changelog: https://github.com/vendurehq/vendure/compare/v3.6.0...v3.6.1
- Back up the database before migration. Run `npx vendure migrate --generate v36`, then edit the generated migration to insert `migrateProductOptionGroupData(queryRunner)` before dropping the `productId` column and `migrateAssetTranslationData(queryRunner)` before dropping the `name` column.
- If using ElasticSearch, upgrade your instance to version 9.1.0 and update `@elastic/elasticsearch` dependency accordingly; a full reindex is required.
- Community plugins must be uninstalled (e.g., `@vendure/elasticsearch-plugin`) and installed from the new `@vendure-community` organization with updated import paths.
- ProductOptionGroup and ProductOption are now shared resources with channel awareness (requires migration of option group data).
- Asset entity becomes translatable; the `name` column is moved to a new `asset_translation` table (migration required).
- GraphQL `productOptionGroups` query now returns paginated `ProductOptionGroupList` instead of a flat array.
- First‑class API key support for machine‑to‑machine authentication with scoped permissions, rotation, and revocation.
- Asset now implements the `Translatable` interface; migration helper copies existing names into translations automatically.
- Configurable order tax calculation via pluggable `OrderTaxCalculationStrategy` (includes `DefaultOrderTaxCalculationStrategy` and new `OrderLevelTaxCalculationStrategy`).
Full changelog
Highlights
Shared Product Option Groups
Option groups are now shared resources — a single "Size" group can be linked to as many products as you need. They're also channel-aware, so multi-tenant setups can scope them per channel. There's a new dedicated management page in the dashboard, and CSV imports handle shared groups out of the box. A migration helper is provided for the data transition.
API Key Authentication
First-class API key support for machine-to-machine authentication. Create a key, scope it to specific permissions, use it in a header. Keys can be rotated and revoked at any time, with a full management UI in the dashboard. Community contribution from Daniel Biegler.
Translatable Assets
Asset now implements the Translatable interface — asset names and custom fields can vary per language. A migration helper copies existing names into translations automatically.
Configurable Order Tax Calculation
Order-level tax calculation is now pluggable via OrderTaxCalculationStrategy. Ships with DefaultOrderTaxCalculationStrategy (same as v3.5) and OrderLevelTaxCalculationStrategy.
Refreshed Dashboard
The dashboard is now built on @vendure-io/ui, our own open-source design system. Same tech stack, refined visual identity. Under the hood, headless primitives migrated from Radix UI to Base UI — all components now live in a single package. New extension points: toolbar items, function-based nav sections, component-based alert actions, improved ActionBar. Translation fallback placeholders for non-default languages. New Hungarian and Dutch translations.
Community Plugins
Several plugins have moved to the @vendure-community npm org with independent versioning. See the migration guide below for the full mapping.
Per-Queue Job Concurrency
Job queue concurrency now accepts a function (queueName: string) => number for per-queue control.
Other Notable Features
BootstrappedEvent— fires when the server is fully ready afterapp.listen()onBeforeAppListenhook — operate on the NestJS app before it starts listening- Async email generators
setOrderCurrencyCodeShop API mutation- Collection search filters (
collectionIds,collectionSlugs) - Braintree multi-currency support
- Custom field
dashboard: { visible: false }option - Settings Store management page
- Force update payment status
EntityAccessControlStrategy(developer preview) — row-level access control
Notable Fixes
- Dashboard compilation: 2x faster builds, 4x lower memory usage (replaced
ts.createProgramwith per-file transpilation) - Atomic, concurrency-safe
mergeOrders - Schema-qualified table paths in
EXISTSsubqueries (multi-schema Postgres fix) - Stale shipping line cleanup for deleted shipping methods
Migration Guide
This guide covers all breaking changes and required migration steps when upgrading from v3.5.x to v3.6.0.
1. Database Migration
Back up your database before proceeding. The migration involves moving data between tables, and while the helpers are idempotent and well-tested, a backup is always good practice before a schema change of this scale.
After updating your Vendure packages to v3.6, generate your migration:
npx vendure migrate --generate v36
This generates a migration file in your configured migrations directory. Before starting your application, you need to edit the generated migration file to insert two data migration helpers. If you skip these steps, the auto-generated DDL will drop columns and data will be permanently lost.
1a. Asset Translation Data
The Asset entity is now translatable (#4171). The name column moves from the asset table to a new asset_translation table.
Open your generated migration file and look for the SQL that creates the asset_translation table. Further down in the same file, you'll find a statement that drops the name column from asset. Insert the migrateAssetTranslationData helper call between these two operations.
1b. Shared ProductOptionGroup Data
ProductOptionGroup and ProductOption are now shared resources that can belong to multiple products, and are channel-aware (#4469). The productId FK column on product_option_group is replaced by join tables.
Look for the SQL that creates the new join tables (e.g. CREATE TABLE "product_option_groups_product_option_group"). Further down, you'll find a statement that drops the productId column. Insert the migrateProductOptionGroupData helper call between these two operations.
Putting it together
Here is a real example of a generated Postgres migration with the helpers inserted. Your migration will look similar — the exact SQL and constraint names will vary, but the structure is the same.
import { MigrationInterface, QueryRunner } from 'typeorm';
import {
migrateAssetTranslationData,
migrateProductOptionGroupData,
} from '@vendure/core';
export class V361774950673940 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
// ... auto-generated DDL ...
await queryRunner.query(`CREATE TABLE "asset_translation" ("createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "languageCode" character varying NOT NULL, "name" character varying NOT NULL, "id" SERIAL NOT NULL, "baseId" integer, CONSTRAINT "PK_2f22e63eefeef14d245bdb956b6" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE INDEX "IDX_4eed4464adef51f53e1c7d8021" ON "asset_translation" ("baseId") `, undefined);
// ... api_key tables, product_option join tables, etc. ...
await queryRunner.query(`CREATE TABLE "product_option_groups_product_option_group" ("productId" integer NOT NULL, "productOptionGroupId" integer NOT NULL, CONSTRAINT "PK_6a7a0291e226fbb0d4df828a483" PRIMARY KEY ("productId", "productOptionGroupId"))`, undefined);
// ... indexes ...
// *** INSERTED: migrate option group data before productId is dropped ***
await migrateProductOptionGroupData(queryRunner);
await queryRunner.query(`ALTER TABLE "product_option_group" DROP COLUMN "productId"`, undefined);
// *** INSERTED: migrate asset names before name column is dropped ***
await migrateAssetTranslationData(queryRunner);
await queryRunner.query(`ALTER TABLE "asset" DROP COLUMN "name"`, undefined);
// ... remaining DDL (foreign key constraints, etc.) ...
}
public async down(queryRunner: QueryRunner): Promise<any> {
// Auto-generated reverse DDL (no changes needed here)
}
}
Tip: In a Postgres migration, look for the two DROP COLUMN lines — they'll be close together:
ALTER TABLE "product_option_group" DROP COLUMN "productId"
ALTER TABLE "asset" DROP COLUMN "name"
Insert each helper call immediately before its corresponding DROP COLUMN.
Note for SQLite users: SQLite doesn't support DROP COLUMN directly. TypeORM uses a temporary table pattern instead (create temp table without the column, copy data, drop original, rename). The same principle applies — insert the helpers before the data copy that omits the column — but the SQL will look different.
Both helpers are idempotent — safe to run multiple times without creating duplicate data. If the migration is interrupted partway through, you can re-run it safely.
Run the Migration
Start your application. The default index.ts scaffold calls runMigrations(config) before bootstrap(config), so the migration will be applied automatically on startup.
Alternatively, you can run it explicitly:
npx vendure migrate --run
2. Community Plugins: New Package Names
Several plugins have been moved out of the Vendure monorepo into a dedicated community repo: vendurehq/vendure-community-plugins. They are now published under the @vendure-community npm org with independent versioning, decoupled from Vendure Core releases.
The plugin APIs are unchanged — same classes, same configuration, same behaviour. This is a package rename, not a rewrite.
If you use any of these plugins, uninstall the old package and install the new one:
| Old package (final version) | New package |
|---|---|
| @vendure/[email protected] | @vendure-community/[email protected] |
| @vendure/[email protected] (Stripe) | @vendure-community/[email protected] |
| @vendure/[email protected] (Braintree) | @vendure-community/[email protected] |
| @vendure/[email protected] (Mollie) | @vendure-community/[email protected] |
| @vendure/[email protected] | @vendure-community/[email protected] |
| @vendure/[email protected] | @vendure-community/[email protected] |
| @vendure/[email protected] (pub-sub) | @vendure-community/[email protected] |
For example, to migrate the Stripe plugin:
npm uninstall @vendure/payments-plugin
npm install @vendure-community/[email protected]
Then update all imports in your TypeScript code:
// Before
import { ElasticsearchPlugin } from '@vendure/elasticsearch-plugin';
import { StripePlugin } from '@vendure/payments-plugin/package/stripe';
// After
import { ElasticsearchPlugin } from '@vendure-community/elasticsearch-plugin';
import { StripePlugin } from '@vendure-community/stripe-plugin';
Search your codebase for any remaining references to the old package names — this includes imports, dynamic require() calls, and any configuration files that reference the old packages.
Note: @vendure/job-queue-plugin (BullMQ) remains in core and continues to be published as part of Vendure. Only the pub-sub strategy has moved.
3. ElasticSearch Plugin: Upgrade to v9.1.0
If you use the ElasticSearch plugin (now @vendure-community/elasticsearch-plugin), you must upgrade your ElasticSearch instance from v7.x to v9.1.0 (#3740).
This is a one-way upgrade with no downgrade path. Test in a staging environment first.
Steps:
- Upgrade your ElasticSearch instance to v9.1.0. If running via Docker, update your image tag (e.g.
elasticsearch:9.1.0). - Update your project's
package.json:"@elastic/elasticsearch": "9.1.0" - The ES database schema migration happens automatically on instance startup.
- After the upgrade, run a full reindex via the Admin API or dashboard
The old v7 client is incompatible with v9 and vice versa. Both the plugin and your project must use the v9.1.0 client.
4. Dashboard: Radix UI → Base UI Migration
The dashboard has migrated from Radix UI to Base UI via the new @vendure-io/ui package (#4531).
If you have custom dashboard extensions using Radix UI components, run the provided codemod:
npx vendure codemod dashboard-base-ui <target-directory>
Important: The codemod scans all .tsx files reachable from the tsconfig. In a monorepo with multiple apps (e.g. a Next.js storefront alongside the Vendure server), running it from the repo root will also transform files in non-dashboard projects. This can break those projects — for example, rewriting import { toast } from 'sonner' to import { toast } from '@vendure/dashboard' in a Next.js app.
To avoid this:
- Run the codemod only on your dashboard extension directories, not the entire repo root. For example:
npx vendure codemod dashboard-base-ui libs/my-dashboard-plugin/ npx vendure codemod dashboard-base-ui src/plugins/ - If you do run it from the repo root, review changes in non-dashboard projects and revert them.
The codemod handles import path updates automatically. Component props that differ between Radix and Base UI may need manual adjustment. After running the codemod, check for any remaining TypeScript errors:
npx tsc --noEmit
If you don't have custom dashboard extensions, no action is needed.
[!NOTE]
when specifying a non-root path to your plugins, you may encounter the errorNo tsconfig.json found in <path>. This is a known issue resolved in the next patch. For now as a workaround you can temporarily copy your dashboard tsconfig file into that directory when running the codemod.
5. Dashboard: Vite v6 → v7
The dashboard's Vite version has been upgraded from v6 to v7 (#4514).
No action required in the typical case. In monorepo environments where other projects depend on a different Vite version, review for potential version conflicts.
6. API & Type Changes
ProductOptionGroups Query — Now Paginated
The productOptionGroups query now returns a paginated ProductOptionGroupList (with items and totalItems) instead of a flat array. It accepts standard ProductOptionGroupListOptions for filtering, sorting, and pagination.
Before (v3.5):
query {
productOptionGroups(filterTerm: "size") {
id
name
}
}
After (v3.6):
query {
productOptionGroups(options: {
filter: { name: { contains: "size" } }
}) {
items {
id
name
}
totalItems
}
}
The old filterTerm parameter is replaced by the standard filter option, which supports all the usual operators (eq, contains, in, etc.).
Asset Type — Now Translatable
The Asset GraphQL type now includes languageCode and translations fields. CreateAssetInput and UpdateAssetInput accept an optional translations array.
Existing code that reads asset.name will continue to work — the name is resolved from the translation matching the current request language.
OrderMergeStrategy — Now Async
OrderMergeStrategy.merge() return type has been widened to MergedOrderLine[] | Promise<MergedOrderLine[]> (#4436). Existing synchronous implementations still work without changes. If you call merge() directly in custom code, you must now await the result:
// Before
const result = myStrategy.merge(ctx, order, guestOrder);
// After
const result = await myStrategy.merge(ctx, order, guestOrder);
Job Queue Concurrency Type Widened
PollingJobQueueStrategy.concurrency type changed from number to number | ((queueName: string) => number) (#4201). The same applies to BullMQ and PubSub strategies. Existing numeric values still work without changes.
7. Deprecations
Health Check Features
The built-in health check features integration have been deprecated (#4442):
HttpHealthCheckStrategy,TypeOrmHealthCheckStrategy, andHealthCheckRegistryServiceare marked@deprecated- The health check page has been removed from the dashboard
- The health check HTTP endpoint still functions for the server and worker.
Why? Checking all infrastructure dependencies (DB, Redis, etc.) in a single health endpoint is actually an anti-pattern in production environments. The core problem: If your database goes down for a few seconds, the health check fails, and your orchestrator (Kubernetes, ECS, etc.) marks the app as unhealthy and restarts or drains it. Now you have two problems: the DB is down and your app instances are being killed, even though the application process itself is perfectly fine. When the DB recovers, there are no healthy instances left to serve traffic.
The solution is to separate the health checks for each piece of your production infrastructure. The Vendure /health endpoints should only report the health of that instance, independent of all other infrastructure.
8. New Opt-In Features
These are new features that don't require migration, but you may want to be aware of:
Anonymous Telemetry
v3.6 adds opt-out anonymous telemetry. It collects non-identifying usage data (strategy types, plugin adoption, feature flags) to help prioritize development. To disable, set the environment variable:
VENDURE_DISABLE_TELEMETRY=true
Default Search Plugin: Currency Code Index
If you want to index by currency code in the default search plugin, you can now opt in (#3268):
DefaultSearchPlugin.init({
indexCurrencyCode: true,
})
This changes the primary key of the search index table and requires a full reindex after enabling.
9. Dependency Updates
The following dependencies of @vendure/core have been updated (#4564). You only need to take action if your project directly imports or depends on these packages. If you only interact with them through Vendure's APIs, no changes are needed.
| Package | From | To |
|---------|------|-----|
| bcrypt | 5.x | 6.x |
| better-sqlite3 | 11.x | 12.x |
| mime-types | 2.x | 3.x |
| csv-parse | 5.x | 6.x |
| i18next | 24.x | 25.x |
| image-size | 1.x | 2.x |
| @graphql-tools/stitch | 9.x | 10.x |
Note: bcrypt is a native addon. After upgrading, you may need to run npm rebuild bcrypt if you encounter errors.
Quick Checklist
- [ ] Back up your database
- [ ] Update all
@vendure/*packages to v3.6.0 - [ ] Replace community plugin packages (
@vendure/elasticsearch-plugin→@vendure-community/elasticsearch-plugin, etc.) - [ ] Update all imports in TypeScript code to use new community plugin package names
- [ ] Generate migration:
npx vendure migrate --generate v36 - [ ] Edit migration file: add
migrateProductOptionGroupData(queryRunner)beforeDROP COLUMN "productId" - [ ] Edit migration file: add
migrateAssetTranslationData(queryRunner)beforeDROP COLUMN "name" - [ ] Start application (or run
npx vendure migrate --run) to apply migration - [ ] If using ElasticSearch: upgrade instance to v9.1.0, update
@elastic/elasticsearchto9.1.0, run full reindex - [ ] If using custom dashboard extensions: run
npx vendure codemod dashboard-base-ui <target-dir>thennpx tsc --noEmit - [ ] If using
productOptionGroupsquery: update to paginated response format - [ ] If enabling
indexCurrencyCode: run full reindex - [ ] Run
npm rebuild bcryptif needed
What's Changed
- feat(default-search-plugin): add support for 'currencyCode' index by @casperiv0 in https://github.com/vendurehq/vendure/pull/3268
- feature(elasticsearch-plugin): adds search options by collection slugs or collection IDs by @alexisvigoureux in https://github.com/vendurehq/vendure/pull/3182
- feat(core): Add collectionIds and collectionSlugs filters to default search plugin by @dlhck in https://github.com/vendurehq/vendure/pull/3945
- refactor: Update e2e tests to use gql.tada features by @HouseinIsProgramming in https://github.com/vendurehq/vendure/pull/3974
- feat(asset-server-plugin): Allow specifying encoding for AssetStorageStrategy by @pujux in https://github.com/vendurehq/vendure/pull/3926
- feat(core): API Keys by @DanielBiegler in https://github.com/vendurehq/vendure/pull/3815
- chore: Update the Vendure ElasticSearch plugin to use ElasticSearch v9.1.0 by @LeftoversTodayAppAdmin in https://github.com/vendurehq/vendure/pull/3740
- chore: Update the Vendure ElasticSearch plugin to use ElasticSearch v9.1.0 by @biggamesmallworld in https://github.com/vendurehq/vendure/pull/4009
- feat(dashboard): Improved extensibility of ActionBar by @michaelbromley in https://github.com/vendurehq/vendure/pull/4049
- feat: Add support for asynchronous email generators by @twlite in https://github.com/vendurehq/vendure/pull/3976
- Mollie: Allow forcefully updating payment status in case webhooks are delayed by @martijnvdbrug in https://github.com/vendurehq/vendure/pull/4104
- 3909 pvp custom fields not showing by @mehringer68 in https://github.com/vendurehq/vendure/pull/4180
- feat(core)!: Make Asset entity translatable by @biggamesmallworld in https://github.com/vendurehq/vendure/pull/4171
- feat(core): Add anonymous telemetry collection module by @dlhck in https://github.com/vendurehq/vendure/pull/4192
- feat(core,job-queue-plugin): Add per-queue concurrency configuration by @biggamesmallworld in https://github.com/vendurehq/vendure/pull/4201
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4396
- fix(core): Fix broken e2e test after master merge by @michaelbromley in https://github.com/vendurehq/vendure/pull/4399
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4429
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4433
- feat(core): Add EntityAccessControlStrategy for row-level access control by @michaelbromley in https://github.com/vendurehq/vendure/pull/4451
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4465
- feat(core): Add Shop API mutation to set order currency code by @michaelbromley in https://github.com/vendurehq/vendure/pull/4466
- feat(core): Provide app instance before app starts listening by @LucidityDesign in https://github.com/vendurehq/vendure/pull/4383
- feat(core): Make ProductOptionGroup & ProductOption shared and channel-aware by @michaelbromley in https://github.com/vendurehq/vendure/pull/4469
- feat(dashboard): Add Settings Store management page by @michaelbromley in https://github.com/vendurehq/vendure/pull/4473
- feat(payments-plugin): Add multi currency support for braintree plugin by @kkerti in https://github.com/vendurehq/vendure/pull/3239
- refactor(core,dashboard): Deprecate built-in health check features by @dlhck in https://github.com/vendurehq/vendure/pull/4442
- feat(dashboard): Add Option Groups management page by @michaelbromley in https://github.com/vendurehq/vendure/pull/4483
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4489
- feat(core): Add configurable OrderTaxSummaryCalculationStrategy by @colinpieper in https://github.com/vendurehq/vendure/pull/4376
- feat(dashboard): Add toolbarItems extension point to app shell header by @michaelbromley in https://github.com/vendurehq/vendure/pull/4496
- feat(dashboard): Support function form for navSections by @michaelbromley in https://github.com/vendurehq/vendure/pull/4491
- feat(core): Support shared product option groups in CSV import by @michaelbromley in https://github.com/vendurehq/vendure/pull/4503
- feat(core): Introduce BootstrappedEvent to signal server readiness by @Draykee in https://github.com/vendurehq/vendure/pull/4498
- feat(core): Allow async
OrderMergeStrategyby @twlite in https://github.com/vendurehq/vendure/pull/4436 - feat(dashboard): Upgrade Vite from v6 to v7 by @dlhck in https://github.com/vendurehq/vendure/pull/4514
- fix(dashboard): Add shared-types and shared-utils to Vite optimizeDeps by @dlhck in https://github.com/vendurehq/vendure/pull/4520
- feat(dashboard): Allow component-based alert actions for hook access by @michaelbromley in https://github.com/vendurehq/vendure/pull/4526
- refactor(dashboard): Migrate from Radix UI to Base UI via @vendure-io/ui by @dlhck in https://github.com/vendurehq/vendure/pull/4531
- feat(cli): Add codemod command for Radix to Base UI dashboard migration by @dlhck in https://github.com/vendurehq/vendure/pull/4536
- fix(dashboard): Follow transitive dependencies in plugin discovery by @michaelbromley in https://github.com/vendurehq/vendure/pull/4545
- feat(dashboard): Translation fallback placeholders for translatable fields by @dlhck in https://github.com/vendurehq/vendure/pull/4549
- fix(core): Server-side translation field-level fallback for empty values by @dlhck in https://github.com/vendurehq/vendure/pull/4551
- fix(dashboard): Fix component styling regressions from Base UI migration by @michaelbromley in https://github.com/vendurehq/vendure/pull/4552
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4555
- feat(core): Expand telemetry with strategy, integration and feature adoption data by @dlhck in https://github.com/vendurehq/vendure/pull/4554
- fix(core): Allow admin re-creation after soft-delete by @HouseinIsProgramming in https://github.com/vendurehq/vendure/pull/4543
- fix(dashboard): Hide dev mode ring offset when not hovered by @michaelbromley in https://github.com/vendurehq/vendure/pull/4558
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4562
- chore(plugins): Update safe plugin dependencies by @michaelbromley in https://github.com/vendurehq/vendure/pull/4565
- chore(core): Dependency updates phases 1-3 by @michaelbromley in https://github.com/vendurehq/vendure/pull/4564
- fix(dashboard): Fix flaky path-alias test and sql.js lockfile resolution by @michaelbromley in https://github.com/vendurehq/vendure/pull/4567
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4568
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4573
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4574
- fix(dashboard): Align styling with design system tokens by @michaelbromley in https://github.com/vendurehq/vendure/pull/4575
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4579
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4581
- feat(dashboard): Add API keys management UI by @michaelbromley in https://github.com/vendurehq/vendure/pull/4583
- fix(dashboard): Fix collection expand e2e test selector by @michaelbromley in https://github.com/vendurehq/vendure/pull/4585
- feat(core): Add migrateAssetTranslationData() helper for v3.6 upgrade by @michaelbromley in https://github.com/vendurehq/vendure/pull/4584
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4587
- fix(core): Upgrade i18next to v26 to remove locize promotional message by @michaelbromley in https://github.com/vendurehq/vendure/pull/4588
- chore(repo): Back-merge master into minor (conflicts) by @github-actions[bot] in https://github.com/vendurehq/vendure/pull/4589
- fix(core): Restore minor-branch dependency versions after merge by @michaelbromley in https://github.com/vendurehq/vendure/pull/4590
- chore: Remove community plugins from monorepo by @michaelbromley in https://github.com/vendurehq/vendure/pull/4591
- fix: Align dashboard and job-queue-plugin versions to 3.5.6 by @michaelbromley in https://github.com/vendurehq/vendure/pull/4592
- docs: Add API keys developer guide by @michaelbromley in https://github.com/vendurehq/vendure/pull/4595
- fix(core): Add missing docs annotations and exports for v3.6 APIs by @michaelbromley in https://github.com/vendurehq/vendure/pull/4597
New Contributors
- @mehringer68 made their first contribution in https://github.com/vendurehq/vendure/pull/4180
- @LucidityDesign made their first contribution in https://github.com/vendurehq/vendure/pull/4383
Full Changelog: https://github.com/vendurehq/vendure/compare/v3.5.6...v3.6.0
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.