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 #4646
- fix(ci): Add gate job to unblock non-package PRs by @michaelbromley in #4649
- fix(docs): Fix broken links and outdated type names in custom form components docs by @gabriellbui in #4648
- docs: Fix typo in navigation-menu arrayToTree code snippet by @gabriellbui in #4593
- fix(core): Enforce usage limits for auto-applied promotions by @HouseinIsProgramming in #4405
- fix: Add missing include nested fragments in order modification preview by @Ryrahul in #4640
- fix: Add dynamic y axis width by @Ryrahul in #4516
- feat(dashboard): Add Romanian translations by @alingabrieldm in #4598
- fix: Add recursive flatten job filter for bull mq when filter is sent… by @Ryrahul in #4523
- fix: Make tab view scrollable by @Ryrahul in #4644
- feat(dashboard): Add bulk cancel action and human-readable duration t… by @Ryrahul in #4361
- chore: Bump @vendure-io/docs-generator to 0.1.1 by @michaelbromley in #4664
- fix(admin-ui): Handle tokenMethod array form when generating ui-config (#4656) by @Draykee in #4663
- fix(dashboard): Inline route literal for tanstack router-generator by @michaelbromley in #4666
- fix(core): Avoid OOM in product-to-channel assign/remove via query relation strategy by @arthur-nesterenko in #4669
- chore: Migrate package management from npm to Bun by @michaelbromley in #4675
- fix(core): Create job queues in onModuleInit instead of onApplicationBootstrap by @michaelbromley in #4680
- fix(core): Prevent coupon usage limit bypass via concurrent checkout race condition by @grolmus in #4660
- fix(core): Resolve customer user via relation instead of email lookup by @grolmus in #4468
- fix(dashboard): Support action bar positioning relative to extensions by @izumi0uu in #4676
- chore: Add local asset storage strategy and its factory to exports by @DanielBiegler in #4671
- fix(core): Handle undefined relation in entity hydrator by @izumi0uu in #4672
- fix(dashboard): Handle empty customFields selection when all fields h… by @Ryrahul in #4652
- fix(dashboard): Add missing Italian translations by @claudiolor in #4645
- feat(dashboard): implement
isFullWidthmetadata prop by @casperiv0 in #4638 - fix(dashboard): Repair wrong-language msgstrs across hr/nb/tr/it/ja/ko/he/ro and harden i18n:apply by @michaelbromley in #4685
- fix(dashboard): correct mistranslated Swedish strings in sv.po by @comega-johan in #4684
- fix(dashboard): Ensure fulfillment arg default value is a string by @kyunal in #4658
- chore(dashboard): Add error messages to update draft mutations by @LucidityDesign in #4381
- fix(core): Make state-machine transitions atomic on hook failure by @michaelbromley in #4689
- fix(dashboard): set default sort on promotions list by @casperiv0 in #4688
- fix(core): Prevent duplicate execution of fast scheduled tasks by @BibiSebi in #4681
- fix(dashboard): Check required permissions when rendering custom page… by @LucidityDesign in #4679
- fix(dashboard): Fall back to original on denied replace extension by @michaelbromley in #4694
- fix(dashboard): Align asset mutation selection set with other detail pages by @michaelbromley in #4695
- fix(core): Save Asset before running custom-field relation updates by @michaelbromley in #4696
- chore(dashboard): Drop direct @base-ui/react imports by @michaelbromley in #4697
New Contributors
- @arthur-nesterenko made their first contribution in #4669
- @izumi0uu made their first contribution in #4676
- @claudiolor made their first contribution in #4645
- @comega-johan made their first contribution in #4684
Full Changelog: v3.6.2...v3.6.3