Skip to content

Releases: vendurehq/vendure

v3.7.3

Choose a tag to compare

@michaelbromley michaelbromley released this 02 Sep 07:47

Vendure v3.7.3 is a patch release containing fixes for eleven reported vulnerabilities (four critical, two high, five medium), the seller Order channel-scoping work for multi-vendor setups, corrections to how shipping lines are handled when a shipping method is deleted or unassigned, monorepo support in the CLI, and a batch of dashboard fixes.

Several of the security fixes change behaviour. If you run a multi-channel deployment, or you register an external/SSO authentication strategy, read Behaviour Changes below before upgrading.

Security

Fixes for the following reported vulnerabilities are in 3.7.3. If you are on an earlier 3.x version, upgrade.

Severity Vulnerability Advisory
Critical Unauthenticated account takeover: any customer account created through an external/SSO authentication strategy could be taken over by "registering" that account's email address with an attacker-chosen password GHSA-wr5h-x3x6-4h23
Critical Cross-channel payment IDOR: an administrator scoped to one channel could issue refunds and perform payment and fulfillment transitions against orders belonging to another channel, moving money out of another tenant's payment gateway GHSA-7qvr-c5vf-xxfh
Critical Cross-channel entity capture: an administrator could assign a Product, Collection, Facet, ProductOptionGroup or ProductVariant belonging to another channel into their own channel, gaining ongoing edit and delete control over it GHSA-422x-jq57-j238
High Cross-channel data disclosure: the duplicateEntity mutation copied the source entity without a channel filter, so an administrator could obtain a full copy of an entity from a channel they cannot access GHSA-f94w-2928-x43p
High Administrator directory disclosure: a channel-scoped administrator could read the whole administrator directory through the Admin API, including names, email addresses and role permissions of staff belonging only to other channels GHSA-37j3-p93w-fq6w
Critical Cross-channel channel management: an administrator holding UpdateChannel or DeleteChannel could rename or permanently delete any channel in the system, regardless of which channels their role was scoped to GHSA-22x4-937q-5fr5
Medium Cross-channel catalog write: a channel-scoped administrator could pass the id of a ProductOptionGroup belonging to another channel to createProductOption, linking a new option into another channel's option group GHSA-gg28-cx38-jxxr
Medium Account enumeration by login timing: the mitigation added in 3.5.3 did not cover accounts which exist but have no native password, so those accounts still answered measurably faster than a non-existent address GHSA-c63h-3vvx-48ph
Medium Permissive CORS default: the default configuration reflects any Origin back with Access-Control-Allow-Credentials: true, and Apollo's CSRF prevention was disabled, allowing Login CSRF and cross-origin response reading GHSA-vr2h-89r2-9rwv
Medium Session token disclosure via job data: the serialized RequestContext stored in job data contained the session token, which any administrator holding ReadSettings or ReadSystem could read and reuse GHSA-32jm-mf7r-7qw5
Medium Stored XSS via SVG upload: @vendure/asset-server-plugin served uploaded SVG files inline with an executable content type, so a malicious upload executed script when its URL was opened GHSA-f4r3-h6jf-4m29

file-type has also been bumped to ^21.3.1 in both @vendure/core and @vendure/asset-server-plugin (#5099) to pick up the fix for GHSA-5v7r-6r5c-r473, an infinite loop on malformed ASF input. This bump renames four MIME types — see Migration below.

Our thanks to the researchers who reported these responsibly.

Core

  • Seller Order channel scoping for multi-vendor setups (#5100, closes #4117) — price adjustments are now applied in the seller's channel, shipping lines are preserved when an order is split, shipping promotions are re-applied to seller orders, and the transaction is preserved when scoping the seller order context.
  • Shipping lines are no longer removed from an order when the shipping method is deleted, and active orders are recalculated when a shipping method is unassigned from a channel (#4492, #4494).
  • Preserve OrderLine relation custom fields when merging orders (#5151).
  • Enqueue the apply-collection-filters job after the transaction commits (#5195), so the worker no longer reads a collection that is not yet visible.
  • Filter the SQLite search index by the request currency (#5149).
  • Prevent exponential re-merge of shared entity references during hydration (#5085).
  • Prevent a NaN discount amount on a cancelled modified OrderLine (#5101).
  • Price every ProductVariant in relation arrays, avoiding a spread RangeError on large arrays (#5058).
  • Check every element in isTranslatable and harden translateDeep against holes in arrays (#5059).
  • Propagate field descriptions to generated Filter and Sort parameters (#5065, closes #5032).
  • Invalidate the stock location channel id cache correctly, and handle invalidation failures (#5087, closes #3324).
  • Guard the initial job-queue status update, log the real error details, and release the concurrency slot even when the update fails (#5167, #5168).
  • Use the default Channel context when creating an Asset from a file stream (#4662).
  • Assign the SuperAdmin and Customer roles in ChannelService.create() (#5095).
  • Enforce the channel permission check on Promotion assign and remove (#5072), and guard against removing promotions from the default channel (#5096).
  • Enforce channel scope on ProductVariant soft-deletion (#5049).
  • Update better-sqlite3 for Node 24 (#5146).

Performance

  • Remove the per-request stock query stampede (#5224). MultiChannelStockLocationStrategy is the default since 3.1.0 and sits on the read path of every storefront product query. Its stock location channel id cache had no in-flight de-duplication, so every stock level on a page missed the cache before any of them had populated it and each issued its own query.

Dashboard

  • Entity selectors refetch on mount, so a newly created entity appears without a page reload (#5190, closes #5177, #5178, #5182).
  • The channel switcher refreshes after channels are deleted, and the active channel is recovered when the current one is deleted (#5181, #5228).
  • Source root resolution for extension compilation: compiled files stay inside outputPath for upward imports, the tsconfig baseUrl is remapped when the source root widens, and the filesystem root is preserved in commonAncestorDir (#5090, closes #5086).
  • Transform JSX when compiling the Vendure config and plugin config imports (closes #3726).
  • Manual payments: the transaction ID is now optional (#5027), the add-payment option is matched exactly (#5175), and the dialog close button works (#5133).
  • Treat readonly custom fields as optional in the Zod schema (#5057, closes #5045).
  • Use updateActiveAdministrator on the profile page, so an administrator without UpdateAdministrator can still edit their own profile (#5055).
  • Remove the arbitrary maximum of 1000 on promotion usage limits (#5226).
  • Forward transformQueryKey and view options from ListPage (#5066).
  • Refresh the tax category selector after mutations (#5178).
  • Show a loading indicator while assets upload.

CLI

  • Resolve hoisted dependencies in monorepo workspaces (#5141, closes #4842). Dependency checks now use require.resolve, detect the monorepo root, and walk up to the workspace root when a local node_modules is absent.

@vendure/create

  • Add a TanStack Start storefront option (#5144).
  • Defer the open import to avoid a Yarn PnP crash on Node 22 (#5207).
  • New projects are scaffolded with an explicit CORS origin allowlist read from a CORS_ORIGINS environment variable.

Asset Server Plugin

  • Add a background colour query parameter for transparent images (#4999).

Additions

  • apiOptions.csrfPrevention enables Apollo Server's built-in CSRF prevention on both APIs. It defaults to false; see Behaviour Changes for what enabling it requires of your clients.
  • Create a customer and address inline on a draft order (#4952).
  • Select multiple channels in the assign-to-channel follow-up dialog (#5036).

Behaviour Changes

No breaking API changes, but several of the security fixes tighten behaviour that was previously permissive.

Customer registration. registerCustomerAccount no longer stores the supplied password when an account already exists for that email address through another authentication strategy and has no password yet. A verification token is emailed to the address instead, and the password is set by passing that token to verifyCustomerAccount. This happens whatever the value of authOptions.requireVerification, so such a customer cannot log in immediately after registering. ...

Read more

v3.7.2

Choose a tag to compare

@michaelbromley michaelbromley released this 03 Aug 17:37

Vendure v3.7.2 is a patch release containing fixes for four reported vulnerabilities (one critical, one high, two medium), a set of channel-scoping fixes on entity update and delete paths, a rewrite of the BullMQ job list query, and a round of dashboard fixes

One of the security fixes changes behaviour for delegated admin accounts — see Behaviour Changes below.

Security

Fixes for the following reported vulnerabilities are in 3.7.2. If you are on an earlier 3.x version, upgrade.

Severity Vulnerability Advisory
Critical Privilege escalation to SuperAdmin: an administrator holding only the delegated UpdateAdministrator permission could reset any other administrator's password — including the SuperAdmin's — and log in as them GHSA-v85r-wfgv-jcqc
High Broken access control: the Admin API adjustDraftOrderLine mutation was reachable by unauthenticated callers, who could change line quantities and custom fields on orders they did not own GHSA-hc75-2v4j-x372
Medium Cross-channel delete IDOR: a channel-restricted administrator could delete another channel's promotions and facet values by enumerating ids GHSA-fp4j-ff6j-9793
Medium Cross-channel write IDOR: a channel-restricted administrator could modify another channel's assets and stock locations by enumerating ids GHSA-rgjm-ff27-p2hf

Our thanks to the researchers who reported these responsibly.

Core

  • Channel-scope guard on StockLocation and Asset update() (#5017) and on the FacetValue, ProductOption and Promotion delete paths (#5043), closing the remaining cases where a channel-restricted administrator could modify or delete an entity in another channel.
  • Assign new variants to all of the product's channels (#4699).
  • Record numeric stockOnHand at the active channel's stock location rather than the global default (#4970).
  • Hydrate relations that are missing from only some array elements (#4986) — EntityHydrator sampled only the first element of an array relation, so unevenly-loaded relations were reported as present and left unloaded.
  • Prevent a route-scoped beforeListen body parser from disabling body parsing on every other route (#5029).
  • Resolve Administrator on API-key sessions via the key owner (#4727).
  • Guard relation custom field resolution against a missing entity id (#5006).
  • Guard mergeDeep against undefined array elements (#4961).

Dashboard

  • Support assigning multiple channels in bulk actions (#4687).
  • Fix stock location deletion, and add stock transfer on delete (#4918).
  • Make Japanese and Korean selectable and complete their translations (#5077).
  • Activate the fallback locale before extensions load (#5061).
  • Compile config outside the type: module package (#4913), fixing dashboard builds failing on Windows/pnpm with exports is not defined in ES module scope.
  • Don't persist empty translations for unfilled languages (#4962).
  • Preserve numeric-looking values in string list inputs (#4988).
  • Search product variants by name or SKU in relation selectors (#4990).
  • Fix required-field validation on the channel create form (#4198).
  • Refresh customer history after updating a customer (#4998).
  • Add a password visibility toggle to all password fields (#4969).
  • Clear asset selection after a bulk action completes (#4941).
  • Admin UI: fix the missing currency button on initial load (#5002).

Job Queue

The BullMQ jobs list query had several defects in its Lua script and index maintenance (#5014): jobs were silently dropped from pages when the result set spanned both list-stored and sorted-set-stored states (the skip offset was applied twice), ordering mixed raw delayed scores with plain timestamps, and the query crashed on queues with more than roughly 8000 waiting jobs.

Behaviour Changes

No breaking API changes, but the fix for GHSA-v85r-wfgv-jcqc tightens updateAdministrator: an administrator can now only update another administrator if they hold all of the target's permissions on all of the target's channels, so updating a SuperAdmin requires a SuperAdmin caller. If you have delegated the UpdateAdministrator permission to a role that edits higher-privileged accounts, those operations will now be rejected. Grant the role the necessary permissions, or perform the update as a SuperAdmin.

Migration

No migrations or config changes are required. Upgrade all @vendure/* packages together to 3.7.2.

What's Changed

  • docs: Add missing 3.7.1 changelog entries by @michaelbromley in #4971
  • fix(core): Guard mergeDeep against undefined array elements by @grolmus in #4961
  • fix(core): Record numeric stockOnHand at active channel's stock location by @grolmus in #4970
  • chore(dev-server): Add worktree-safe Portless workflow by @dlhck in #4978
  • fix(dashboard): add password visibility toggle to all password fields by @latifniz in #4969
  • fix(dashboard): Clear asset selection after bulk action completes by @latifniz in #4941
  • test(core): Add unit tests for default shipping line assignment strategy by @GabrielRoc in #4926
  • test(core): Add unit tests for default shipping calculator by @GabrielRoc in #4925
  • test(core): Add unit tests for contains-products promotion condition by @GabrielRoc in #4881
  • test(core): Add unit tests for customer-group promotion condition by @GabrielRoc in #4883
  • test(core): Close branch coverage gaps in order tax calculation strategies by @GabrielRoc in #4924
  • test(core): Add unit tests for has-facet-values promotion condition by @GabrielRoc in #4882
  • test(core): Add unit tests for minimum-order-amount promotion condition by @GabrielRoc in #4880
  • perf(dev-server): Reduce agent dev startup builds by @dlhck in #4989
  • fix(dashboard): Fix required-field validation on the channel create form by @biggamesmallworld in #4198
  • feat(core): expand telemetry to schema v2 with heartbeat and new signals by @dlhck in #4933
  • fix(dashboard): Refresh customer history after updating customer by @oliverstreissi in #4998
  • docs(core): Document creating a RequestContext for a specific user by @grolmus in #5008
  • fix(dashboard): Preserve numeric-looking values in string list inputs by @kwerie in #4988
  • fix(core): Guard relation custom field resolution against a missing entity id by @brmk in #5006
  • fix(core): Assign new variants to all product channels by @Ryrahul in #4699
  • fix(dashboard): Don't persist empty translations for unfilled languages by @grolmus in #4962
  • fix(job-queue-plugin): Fix BullMQ job list query pagination, ordering & index maintenance by @michaelbromley in #5014
  • chore: Lint pull request titles by @dlhck in #5016
  • fix(core): Resolve Administrator on API-Key sessions via key owner by @kevmtt in #4727
  • docs(core): Fix description for inner workings of DefaultMoneyStrategy by @SiebelsTim in #4929
  • docs(dashboard): Document contributing UI translations to the Dashboard by @grolmus in #5010
  • docs: direct security reports to GitHub advisories by @dlhck in #5020
  • fix(dashboard): search product variants by name or SKU in relation selectors by @casperiv0 in #4990
  • docs: Add missing path import to importing-data test-data example by @grolmus in #5021
  • fix(core): Prevent route-scoped beforeListen parser from disabling body parsing by @michaelbromley in #5029
  • fix(core): Add channel-scope guard to StockLocation & Asset update() by @Ryrahul in #5017
  • fix(core): Hydrate relations missing from only some array elements by @ryandiginomad in #4986
  • fix(dashboard): Fix stock location deletion and add stock transfer on delete by @biggamesmallworld in #4918
  • fix(create): Read the server port from VENDURE_SERVER_PORT by @michaelbromley in #5046
  • fix(core): Add channel-scope guard to delete paths & cross-channel re… by @Ryrahul in #5043
  • fix(dashboard): Compile config outside the type:module package by @TheStreamCode in #4913
  • fix(admin-ui): Fix missing currency button on initial load (#4140) by @supermadu7 in #5002
  • fix(dashboard): Activate fallback locale before extensions by @dlhck in #5061
  • feat(dashboard): support...
Read more

v3.7.1

Choose a tag to compare

@michaelbromley michaelbromley released this 14 Jul 13:34

Highlights

A maintenance patch on top of 3.7.0. It fixes a batch of core, dashboard, and @vendure/create issues, moves a stray type dependency in the email plugin into dependencies, and adds request-scoped DataLoader batching when resolving relation custom fields. Thanks to everyone who reported bugs and sent in PRs.

Core

  • Public API type dependencies delivered to consumers (#4947) — types that the public API depends on are now shipped, fixing broken consumer type resolution.
  • Hydration merges shared entity instances (#4945) — shared entity instances are merged into every hydration target, fixing stale/partial hydrated relations.
  • Non-list ID config args no longer double-encoded (closes #2483, #4856) — corrects ID encoding for non-list configurable-operation args.
  • Tax-inclusive shipping cancellation (#4370) — cancelling shipping with pricesIncludeTax now uses the tax-inclusive amount.
  • Promotion condition threshold validation (#4922) — minimum threshold is now validated in promotion conditions.

Dashboard

  • Remove from group bulk action for customer group members (#4346).
  • Option groups can be removed from the product detail page (#4876).
  • Draft order editing no longer removes a line accidentally when changing quantity (#4345); modified line quantities are reflected in the refund dialog (#4917).
  • i18n — added several missing translation strings (#4074) and localised the configurable-operation selector buttons (#4914).
  • Experimental bundle mode now shares context libraries correctly (#4967).

Other

  • @vendure/create — fixed Quick Start hangs and package-manager install failures (#4946).
  • email-plugin@types/nodemailer moved into dependencies (#4942).

Performance

  • Relation custom fields are now resolved with request-scoped DataLoader batching, cutting redundant queries.

What's Changed

  • chore: Consolidate agent skills directory by @dlhck in #4911
  • fix(core): Use tax-inclusive amount when cancelling shipping with pricesIncludeTax by @BibiSebi in #4370
  • fix(dashboard): Reflect modified line quantities in refund dialog by @grolmus in #4917
  • [FIX] Save and serve files with correct Content-Type by @kwerie in #4404
  • fix(core): validate minimum threshold in promotion conditions by @Sanjuhub in #4922
  • fix(core): Export and register Province entity and ProvinceService by @grolmus in #4857
  • fix: assign facets to channel when re-importing products via CSV by @Ryrahul in #4713
  • Fix typo in channel-aware entities description by @mistuttodos in #4927
  • fix(dashboard): Add several missing translation strings by @lucatk in #4074
  • test(core): add unit tests for buy-x-get-y-free promotion condition by @GabrielRoc in #4879
  • fix(dashboard): Localize configurable operation selector buttons by @grolmus in #4914
  • fix(core): Stop double-encoding non-list ID config args by @ryandiginomad in #4886
  • ci: skip docs workflows on draft PRs by @BibiSebi in #4938
  • fix(core): retain v3.6 migration helpers to avoid breaking committed migrations by @michaelbromley in #4939
  • fix(create): Fix Quick Start hangs and package-manager install failures by @michaelbromley in #4946
  • fix(email-plugin): Move @types/nodemailer to dependencies by @grolmus in #4942
  • chore: Align root @types/nodemailer pin with nodemailer@9 by @grolmus in #4944
  • chore: Share CI-aware vitest timeout and worker settings across packages by @michaelbromley in #4948
  • fix(core, asset-server-plugin): Deliver public API type dependencies to consumers by @michaelbromley in #4947
  • fix(core): Merge shared entity instances into every hydration target by @ryandiginomad in #4945
  • chore(core): Fix flaky e2e search-index and scheduler tests by @michaelbromley in #4950
  • fix(dashboard): Allow removing an option group from the product detail page by @grolmus in #4876
  • perf(core): resolve relation custom fields using request-scoped DataLoader batching by @supermadu7 in #4923
  • fix(dashboard): Add "Remove from group" bulk action to customer group members by @biggamesmallworld in #4346
  • fix(dashboard): Prevent accidental line removal when editing draft order quantity by @biggamesmallworld in #4345
  • docs: Fix typo in CMS integration plugin guide by @grolmus in #4963
  • chore: Correct 2FA session-window assumption in stage approve script by @michaelbromley in #4966
  • fix(dashboard): share context libraries in experimental bundle mode by @michaelbromley in #4967
  • fix: resolve SonarCloud reliability issues in maintained packages by @michaelbromley in #4968

New Contributors

Full Changelog: v3.7.0...v3.7.1

v3.7.0

Choose a tag to compare

@michaelbromley michaelbromley released this 01 Jul 10:56

Highlights

Vendure Core v3.7 focuses on developer experience, dashboard extensibility, and security hardening. It adds first-class CLI commands for running and building a project, a doctor diagnostic command, AI-assisted project scaffolding, an opt-in pre-bundled dashboard, new dashboard extension points, a round of supply-chain and dependency hardening, disclosed fixes for several reported vulnerabilities, and two new pluggable strategies.

It's a minor release, so a handful of changes need your attention before upgrading — see Breaking Changes at the bottom.

Developer Experience

CLI lifecycle commands

Three new commands run a Vendure project without hand-rolled ts-node / concurrently scripts (#4774):

  • vendure dev — server, worker, and dashboard with auto-reload (backend files, .env, dashboard extensions; Node inspector flags supported). --no-reload to disable watching.
  • vendure build — compiles server, worker, and dashboard for production (tsconfig discovery, separate worker tsconfig, --clean, --watch, --verbose).
  • vendure start — runs the compiled server and worker with prefixed output and coordinated shutdown.

The add, migrate, schema, and codemod commands now fail fast in non-interactive environments (CI, AI agents) instead of hanging on a prompt.

vendure doctor

A new diagnostic command that runs checks against a project and reports pass / warn / fail / skip (#4777). Five checks by default — Project, Dependencies, Config, Schema, Database — plus a Production profile via --profile production. Supports --check, --format json, and --strict for CI use.

AI-assisted development & more package managers

  • Vendure skills for AI agents: run npx skills add vendurehq/vendure
  • New projects from @vendure/create now include an AGENTS.md context file for AI coding agents (#4850).
  • @vendure/create now supports bun, pnpm, and yarn in addition to npm (#4877).

Pre-bundled dashboard (experimental, opt-in)

Set useExperimentalBundle: true on vendureDashboardPlugin to ship the dashboard as a pre-built ESM bundle instead of thousands of source modules (#4719). Measured on a fresh project with one extension, cold-loading /dashboard/:

Metric Source mode (default) Bundle mode Change
Network requests 3,047 39 −98.7%
DOMContentLoaded 897 ms ~370 ms −59%
JS heap used 168 MB ~95 MB −43%

The default is unchanged; this is opt-in and experimental while we test it in the wild.

Dashboard Improvements

  • Custom React providers (#4600) — extensions can register providers rendered around the dashboard at app or layout level, with explicit ordering, via defineDashboardExtension().
  • User stylesheets (#4765, #4905) — a new theme.additionalStylesheets option on vendureDashboardPlugin injects your CSS through the dashboard's Tailwind v4 pipeline.
  • Configurable DataTable column defaults (#4197) — plugins can set default column visibility and order for data tables via the Plugin Extension API.
  • Configurable TanStack Router options (#4862) — a new tanstackRouterPluginOptions option; also fixes dashboard builds failing with EXDEV: cross-device link not permitted when node_modules is on a different volume.
  • Focal point editor in the asset preview dialog (#4755).
  • New Uzbek translation (#4837) and additional Russian order-action strings (#4838).

Security Hardening

Dependency audit

We audited Vendure's full dependency footprint (#4761), from a baseline of ~1,880 unique production packages, using four rules: remove unused, replace with Node built-ins, vendor small pure-code packages, keep security-sensitive/canonical ones. Results:

  • ~145 transitive dependencies removed with no behaviour change (#4762) — dropping unused body-parser, recategorising mislabelled @types/*/faker, and replacing node-fetch/form-data with native fetch/FormData.
  • Curated telemetry instrumentation set (#4764) — ship the seven instrumentations that match the Vendure stack instead of 40+; opt in to the rest.
  • Dropped @nestjs/terminus (#4771), replaced its health check with a direct SELECT 1.
  • Vendoring / native replacements for change-case, graphql-fields, progress, and tcp-port-used (#4767, #4768, #4769, #4770).

Clearing npm audit warnings

Runtime dependencies were updated to clear npm audit warnings a fresh install reports (#4906) — most notably mjml 4 → 5 (removing the unmaintained html-minifier) and nodemailer 6 → 9, plus version-floor bumps for i18next-fs-backend and i18next-http-middleware.

npm audit Before (3.6.4) After
Critical 0 0
High 42 9
Moderate 6 5
Total 48 14

Reported vulnerabilities

Fixes for the following reported vulnerabilities are all in 3.7.0. Where the fix also landed on the 3.6.x line, it's available in the latest 3.6.x patch, so you can take the fix without upgrading to the minor.

Severity Vulnerability Advisory Fixed in
Critical External-authentication account takeover: an external/SSO login could be linked to a pre-existing account by email without verification GHSA-6j36-r6pr-59x4 3.7.0
High Stored XSS in the dashboard from unsafe HTML-stripping of entity descriptions GHSA-xhq9-whgq-49j5 3.7.0 and latest 3.6.x
High Unauthenticated denial of service (ReDoS) via the regex filter on SQLite backends GHSA-jgm3-qmp2-c4p7 3.7.0 and latest 3.6.x
High File-upload type bypass: uploads accepted on the Content-Type header without verifying contents GHSA-88rq-mq4v-frmm 3.7.0 and latest 3.6.x
Moderate Shop API list queries could return non-public entities when filterOperator was OR GHSA-xf65-r35x-wmmv 3.7.0 and latest 3.6.x

Our thanks to the researchers who reported these responsibly.

Other security fixes

  • Channel scoping on entity updates (#4821) — AssetService.update and StockLocationService.update now scope the lookup by channel, closing a gap where a channel-restricted administrator could modify an entity in another channel.
  • Refuse the default superadmin password in production (#4718) — see Breaking Changes.

New Pluggable Strategies

Both ship with a default that reproduces the existing behaviour exactly, so neither changes anything unless you opt in.

  • CustomerChannelAssignmentStrategy (#4863) — control whether an authenticated customer is auto-assigned to a channel they target but aren't a member of. Return false from canAssignCustomerToChannel() to let them use the channel for the session without becoming a member (useful for strict B2B/multi-channel separation). Configure via authOptions.customerChannelAssignmentStrategy.
  • OrderLineDiscountDistributionStrategy (#4818) — control how an order-level discount is distributed across order lines. The default weights each line by its current prorated price. If you need refunds to reconcile exactly with the order total, you can now supply a strategy that weights by originally-placed quantity instead. Configure via orderOptions.orderLineDiscountDistributionStrategy.

Other Notable Changes

  • CouponRemovedDuringCheckoutError added to AddPaymentToOrderResult (#4683) — handle a coupon becoming invalid mid-checkout.
  • Pooled SMTP options in the email plugin (#4861).
  • Exclude draft orders from promotion usage-limit counts (#4854).
  • Prevent duplicate-key errors in DefaultSearchPlugin index writes (#4809).
  • Seed StockLevel on variant channel assignment (#4864).
  • Prevent unbounded i18next preload growth (#4824) — memory-leak fix for long-running servers.

Breaking Changes

v3.7 is a minor release, but a few changes need your attention.

  • Coupon codes are now compared case-insensitively (#4419), bringing Vendure in line with other commerce platforms. This only affects you if you've used different-case versions of the same code for distinct promotions — those will now collide.
  • The default superadmin password is refused in production (#4718). If your production environment still uses the default superadmin password, Vendure will not start. Change it before upgrading.
  • External authentication now requires a verified email to link to an existing account. As part of the fix for the account-takeover advisory (GHSA-6j36-r6pr-59x4), an external login is only linked to a pre-existing account when the external email is verified. If you have a custom AuthenticationStrategy, it must set verified: true on the returned user data for provider-verified emails, otherwise the link is refused. Creating new accounts is unchanged.
  • Email plugin dependency upgrades: mjml 4 → 5 and nodemailer 6 → 9. If you use MJML email templates or a custom nodemailer transport configuration, review these major upgrades for behavioural changes.
  • @nestjs/terminus is no longer a transitive dependency - since we deprecated health checks for external components in v3.6, we were able to eliminate this dependency from Vendure core. However, if you have your own health check code which depends on this package, you'll need to add @nestjs/terminus as a direct dependency of your project rather than relying on it being present due to Vendure Core.

##...

Read more

v3.6.5

Choose a tag to compare

@michaelbromley michaelbromley released this 01 Jul 09:29

Highlights

This is a security patch release. It fixes three security advisories — a stored XSS in the Admin Dashboard, an unauthenticated denial-of-service affecting SQLite-backed instances, and a data-exposure issue in Shop API list queries — alongside a solid batch of dashboard and core fixes. If you're running 3.6.x, we recommend upgrading. Thanks to those who disclosed the security issues responsibly, and to everyone who reported bugs and sent in PRs.

Security

Three advisories are addressed in this release. Upgrading to 3.6.5 applies all three fixes.

  • Stored XSS in the Admin Dashboard (GHSA-xhq9-whgq-49j5) -- certain stored entity content rendered in the dashboard could carry unsanitised HTML. Rendering has been hardened so stored content can no longer execute script.
  • Denial-of-service on SQLite (GHSA-jgm3-qmp2-c4p7) -- a crafted list-query filter could trigger catastrophic backtracking and tie up the server. Input handling in the affected filter has been hardened. This only affects instances using the SQLite driver, which is intended for development and testing; production deployments on PostgreSQL or MySQL/MariaDB are unaffected.
  • Data exposure in Shop API list queries (GHSA-xf65-r35x-wmmv) -- under certain filter combinations, Shop API list queries could return entities that should not be publicly visible. List-query scoping has been corrected so only intended results are returned.

Core fixes

Notable fixes to the core:

  • Channel scoping enforced on entity updates (#4821) -- update operations now consistently respect channel scoping, tightening isolation between channels in multi-channel setups.
  • Draft orders excluded from promotion usage limits (#4854) -- draft orders were being counted toward a promotion's usage limit, which could exhaust it prematurely. They're no longer counted.
  • Reliable search index writes (#4809) -- DefaultSearchPlugin could hit duplicate-key errors while writing index entries under some conditions. Index writes are now handled without those collisions.
  • Bounded i18next preload (#4824) -- prevented unbounded growth in the i18next preload set, which could accumulate memory over time.
  • StockLevel seeded on channel assignment (#4864) -- assigning a ProductVariant to a channel now seeds its StockLevel for that channel, so stock is tracked from the start.

Dashboard improvements

  • Draft orders (#4810, #4834) -- selecting a customer on a draft order now applies their default shipping and billing addresses.
  • List pages and filters (#4843, #4845, #4839) -- enum fields are now supported in list filters and form defaults; the product multi-selector shows the full saved selection; and order-list state cells are guarded against an undefined state so the list renders cleanly.
  • Robustness (#4859, #4851, #4874) -- the rich-text editor guards getHTML() against a destroyed instance; useUiLanguageLoader is stabilised to stop a render loop; and opening a detail entity that isn't in the active channel now redirects back to the list instead of erroring.
  • Assets (#4855, #4439) -- hard-loading /dashboard/assets now serves the SPA route correctly, and the asset picker dialog isolates its PageContext so it no longer interferes with the page behind it.
  • Layout and polish (#4829, #4712) -- PageBlock renders full-width with column="full", and the facet value chip's remove button stays visible on long values.
  • Internationalisation (#4838, #4870) -- added missing Russian translations for order actions, and fixed a nested t macro inside a plural for the job-cancel toasts.

What's Changed

  • fix: Prevent facet value chip remove button from being hidden on long… by @Ryrahul in #4712
  • fix(core): Stop silent telemetry data loss on Vercel/Netlify and under ESM by @dlhck in #4804
  • fix(core): Prevent duplicate-key errors in DefaultSearchPlugin index writes by @IsroilovA in #4809
  • fix(dashboard,i18n): improve Czech translations by @Misioka in #4806
  • fix(core): Enforce channel scoping on entity update operations by @michaelbromley in #4821
  • fix(core): Prevent unbounded i18next preload growth by @michaelbromley in #4824
  • fix(dashboard): render full-width PageBlock with column="full" by @michaelbromley in #4829
  • fix(dashboard): only send edited stock levels on variant update by @michaelbromley in #4834
  • fix(dashboard): Guard order list state cells against undefined state by @grolmus in #4839
  • fix(dashboard): Serve Assets SPA route when hard-loading /dashboard/assets by @grolmus in #4855
  • fix(core): Exclude draft orders from promotion usage limit counts by @grolmus in #4854
  • fix(dashboard): Stabilise useUiLanguageLoader callback to prevent render loop by @TheStreamCode in #4851
  • fix(email-plugin): Add pooled SMTP options to SMTPTransportOptions by @grolmus in #4861
  • fix(core): seed StockLevel on channel assignment (multi-channel) by @michael-attal in #4864
  • docs: Expand dashboard extension docs by @dlhck in #4866
  • fix(dashboard): Guard getHTML() against destroyed rich text editor by @xakmen in #4859
  • fix(dashboard): Support enum fields in list filters and form defaults by @grolmus in #4843
  • fix(dashboard,i18n): Add missing Russian translations for order actions by @IsroilovA in #4838
  • fix(dashboard): Stop nesting t macro inside plural for job cancel toasts by @michaelbromley in #4870
  • fix(dashboard): redirect to list when detail entity not found in active channel by @michaelbromley in #4874
  • fix(dashboard): Show full saved selection in product multi-selector by @grolmus in #4845
  • fix(dashboard): Isolate PageContext in asset picker dialog by @grolmus in #4439
  • fix(create): Detect package manager for dependency install by @grolmus in #4877
  • fix(dashboard): Set default addresses when selecting customer on draft order by @Lennix in #4810
  • fix(dashboard): Gate draft-order address unset on streetLine1 + re-extract i18n by @michaelbromley in #4890
  • fix(create): Allow pnpm to build native deps in generated project by @michaelbromley in #4893
  • fix(core): Accept SVG uploads rejected by content-type validation by @michaelbromley in #4899
  • fix(testing): Move contentTypeOverrides doc off inline type to fix docs build by @michaelbromley in #4900
  • fix(create): install workspace deps from root in monorepo mode by @michaelbromley in #4907

New Contributors

Full Changelog: v3.6.4...v3.6.5

v3.6.4

Choose a tag to compare

@michaelbromley michaelbromley released this 01 Jun 15:05

Highlights

This patch release is mostly about hardening — both the runtime and the release pipeline itself — together with a big batch of dashboard fixes and a couple of performance wins. It also reverts a graphql dependency change from v3.6.3 that caused trouble in some setups. Thanks to everyone who reported issues and sent in PRs.

Reverting the graphql dependency change

v3.6.3 narrowed the graphql version range used by the core packages, and that turned out to break a number of repos with otherwise perfectly valid setups. We've widened it back to ^16.11.0 (#4716). If you pinned or added an override to graphql to get v3.6.3 working, you can take that workaround out now.

Security

  • SSRF blocked in asset import (#4721) -- DefaultAssetImportStrategy would fetch whatever URL it was handed, which left room for server-side request forgery when importing assets from untrusted input. It now refuses requests aimed at internal and private network addresses.

In light of a recent increase in supply chain attacks on the npm ecosystem, a good chunk of the work this cycle went into the release pipeline rather than the code you run. We've moved to staged npm publishing, pinned all GitHub Actions to specific commit SHAs, dropped off the deprecated Node 20 runtime, scoped the GitHub App tokens down to least privilege, and gated the publish trigger to maintainers (#4784, #4785, #4786, #4787, #4788, #4789). None of this changes how Vendure behaves, but it tightens up the supply chain around how releases are built and shipped.

Core fixes

Notable fixes to the core:

  • Inherited QueryRunner no longer released early (#4717) -- When a TransactionWrapper inherited a QueryRunner from an enclosing transaction, it would release that runner when it finished, affecting the outer transaction. It now only releases runners it created itself.
  • Order variant names respect translations (#4738) -- OrderService.findOne wasn't loading ProductVariant translations, so variant names could come back in the wrong language. The translations are now loaded as expected.
  • Asset translation migration skips empty databases (#4733) -- The v3.6 asset-translation migration helper threw when run against a fresh, empty database. It now detects that case and skips cleanly.

Performance

  • findByCustomerId n+1 fixed (#4653) -- Filtering on productVariant relations in findByCustomerId was triggering an n+1 query. The relations are now fetched in a single query.
  • Lighter dashboard relation query (#4743) -- A heavy relation query behind one of the dashboard list views has been trimmed down, reducing load on larger datasets.

Dashboard improvements

  • Custom fields (#4407, #4734, #4431, #4781) -- A round of custom-field fixes: relational custom fields now persist when saving a draft order; custom fields show up on the profile page now that addCustomFields is applied to its route loader; fields the current user isn't allowed to write are stripped from the mutation before submit instead of triggering a permission error; and struct custom fields render properly in narrow containers rather than overflowing.
  • Form inputs (#4801, #4794, #4759, #4750) -- Select inputs backed by a nullable field can be set back to the empty option; boolean configurable args now initialise with the correct value instead of being left undefined; AffixedInput forwards onFocus and onKeyDown so consumers can hook into those events; and the bulk facet value modal no longer loses focus to its dropdown menu on keydown.
  • List pages (#4746, #4739) -- Multiple values within a single column filter now combine with OR, so filtering on several values returns the union as expected. Column visibility settings also persist properly, alongside a Dialog title context fix.
  • AlertsProvider reacts to late-registered extensions (#4747) -- Alerts registered by extensions after the provider mounts are now picked up.
  • Tiptap toolbar tracks editor state (#4705) -- The rich-text toolbar now reflects the current selection's formatting state instead of going stale.
  • Less layout shift from remote data (#4707) -- Reduced the flicker and layout shift that showed while remote data was loading.
  • Static assets skip rate limiting (#4709) -- Static dashboard assets bypass the rate limiter and get a long-lived cache header.
  • Quieter logs (#4745) -- The dashboard no longer floods logs with "Uncompiled message detected" warnings.
  • Windows .po glob patterns normalised (#4751) -- Translation file globs now use forward slashes on Windows so they resolve correctly.
  • Administrator search and seller subtitle (#4778) -- Administrator search now spans multiple fields, and the empty seller subtitle has been tidied up.
  • Filter and master toggle on variant generation (#4752) -- The variant generation table now has a filter and a master toggle for selecting rows in bulk.-

What's Changed

  • fix(dashboard): fix tiptap toolbar not reacting to state changes by @Knitesik in #4705
  • fix(dashboard): Reduce layout shift and flicker from remote data by @dlhck in #4707
  • fix(create): Add vite as direct dev dependency in scaffolded projects by @grolmus in #4710
  • fix(dashboard): Skip rate limit for static assets and add long-lived cache by @grolmus in #4709
  • docs: Revise README by @dlhck in #4737
  • fix(core): Load ProductVariant translations in OrderService.findOne by @grolmus in #4738
  • fix(dashboard): Apply addCustomFields to profile page route loader by @grolmus in #4734
  • Fix relational custom field not saving on draft order by @Ryrahul in #4407
  • perf(dashboard): reduce heavy relation query by @dublin74 in #4743
  • fix(dashboard): Preserve column visibility and fix Dialog title context by @grolmus in #4739
  • fix(core): Block SSRF in DefaultAssetImportStrategy by @grolmus in #4721
  • fix(core): prevent double-decoding of ID args in updatePromotion by @Ryrahul in #4740
  • fix(core): Skip v3.6 asset translation migration on empty DB by @grolmus in #4733
  • fix(dashboard): normalize glob patterns to forward slashes on Windows by @Yanis02015 in #4751
  • fix(dashboard): Make AlertsProvider react to extensions registered after mount by @grolmus in #4747
  • fix(dashboard): Stop logging "Uncompiled message detected" warnings by @grolmus in #4745
  • fix(core): Do not release inherited QueryRunner in TransactionWrapper by @grolmus in #4717
  • fix(dashboard): stop keydown propagation in facet values dialog to prevent DropdownMenu focus steal by @Ryrahul in #4750
  • fix(dashboard): Apply column filters on list pages using OR filter operator by @grolmus in #4746
  • docs: Add deployment overview and fix deployment guides by @dlhck in #4782
  • chore: Enable npm staged publishing for releases by @michaelbromley in #4784
  • ci: Restrict npm publish dispatch to maintainers (Tier 2) by @michaelbromley in #4785
  • ci: Pin all GitHub Actions to SHA and bump off deprecated Node 20 runtime by @michaelbromley in #4786
  • ci: Scope GitHub App tokens to least privilege by @michaelbromley in #4787
  • fix(ci): Drop actions:write from CLA app token (regression from #4787) by @michaelbromley in #4789
  • ci: Gate the npm publish release trigger to maintainers by @michaelbromley in #4788
  • fix(dashboard): prevent struct custom field overflow in narrow contai… by @Ryrahul in #4781
  • ci: Fix intermittent dashboard port race in Publish & Install by @michaelbromley in #4790
  • fix(core, dashboard): Widen graphql version range to ^16.11.0 by @grolmus in #4716
  • fix: Remove non permitted customfield from mutation before submit by @Ryrahul in #4431
  • fix(dashboard): initialize boolean configurable args by @genm in #4794
  • fix(dashboard): forward onFocus and onKeyDown in AffixedInput by @Ryrahul in #4759
  • fix(services): findByCustomerId productVariant relation filter breaks by @harshit078 in #4653
  • fix(dashboard): Resolve logout stuck in verifying state on failure by @calebcgates in #4757
  • fix(admin-ui): remove puppeteer dependency and rely on system Chrome for Karma tests by @michaelbromley in #4798
  • fix(ci): Install chromium-headless-shell for dashboard e2e by @michaelbromley in #4800
  • feat(dashboard): Filter + master toggle on variant generation table by @grolmus in #4752
  • fix: add administrator and seller to createEntityConfigs by @Ryrahul in https://github.com/vendurehq/vendure/...
Read more

v3.6.3

Choose a tag to compare

@michaelbromley michaelbromley released this 05 May 15:17

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) -- assignProductsToChannel and removeProductsFromChannel now 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 from onApplicationBootstrap to onModuleInit means 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.user resolver 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 tokenMethod array form (#4663, fixes #4656) -- When tokenMethod is configured as an array (['cookie', 'bearer']), the generated ui-config now 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 requiredPermissions and won't render for users who lack them.
  • isFullWidth metadata prop implemented (#4638) -- PageBlocks can now opt into a full-width layout via metadata, useful for components like rich text editors.
  • Direct @base-ui/react imports 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 customFields selection 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, he and ro removes msgstr entries that had drifted into the wrong language. The i18n:apply script has been hardened to prevent recurrences.

Plugins

  • BullMQJobQueuePlugin filtering (#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 isFullWidth metadata 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 @kyuna...
Read more

v3.6.2

Choose a tag to compare

@michaelbromley michaelbromley released this 13 Apr 09:24

This is a high-priority security patch that addresses a vulnerability that was reported a few days ago, described in 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 #4624

Full Changelog: v3.6.1...v3.6.2

v3.6.1

Choose a tag to compare

@michaelbromley michaelbromley released this 09 Apr 08:11

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.
image

CLI codemod fix

  • tsconfig resolution in the migration codemod (#4599) -- The @vendure/cli codemod for migrating dashboard extensions now correctly resolves tsconfig.json by 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) -- assignProductsToChannel was 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 OrderSplitter could 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 behind ReadOrder.

What's Changed

  • ci: Fix Playwright install in publish_and_install workflow by @michaelbromley in #4603
  • ci: Use cd instead of working-directory for Playwright install by @michaelbromley in #4605
  • feat(dashboard): Support Zod v4 and re-export Zod from @vendure/dashboard by @dlhck in #4607
  • fix(ci): Remove [skip ci] from docs manifest commit to unblock PR checks by @dlhck in #4611
  • fix(dashboard): Use index-based identification for collection filters by @grolmus in #4428
  • fix(dashboard): Skip validation for unchecked variant rows by @michaelbromley in #4610
  • chore(dashboard): Sync i18n catalogs by @michaelbromley in #4613
  • fix(cli): Resolve tsconfig by walking up from target directory by @michaelbromley in #4599
  • fix(dashboard): Fix collection filter e2e test selector by @michaelbromley in #4615
  • feat(dashboard): Allow creating single variant without option groups by @michaelbromley in #4616
  • fix(ci): Tolerate i18n line reference changes in sync check by @michaelbromley in #4617
  • fix(core): Assign Product entity to channel in assignProductsToChannel by @michaelbromley in #4618
  • fix(dashboard): Fix option group edit link on variant detail page by @michaelbromley in #4620
  • refactor: Replace local docs generation code with @vendure-io/docs-generator by @oliverstreissi in #4626
  • fix: Make address dialog scrollabe with max h by @Ryrahul in #4622
  • fix: Match query key invalidation by @Ryrahul in #4630
  • fix(core): Deduplicate channels in OrderSplitter when channelId matches default (#4631) by @Draykee in #4632
  • fix: Add creator portal in toaster to avoid isolation:isolate stacking by @Ryrahul in #4634
  • feat(dashboard): Add requiresPermissions support to dashboard widgets by @niko91i in #4627

Full Changelog: v3.6.0...v3.6.1

v3.6.0

Choose a tag to compare

@michaelbromley michaelbromley released this 31 Mar 14:36

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 after app.listen()
  • onBeforeAppListen hook — operate on the NestJS app before it starts listening
  • Async email generators
  • setOrderCurrencyCode Shop 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.createProgram with per-file transpilation)
  • Atomic, concurrency-safe mergeOrders
  • Schema-qualified table paths in EXISTS subqueries (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/elasticsearch-plugin@3.5.6 @vendure-community/elasticsearch-plugin@1.1.0
@vendure/payments-plugin@3.5.6 (Stripe) @vendure-community/stripe-plugin@1.0.0
@vendure/payments-plugin@3.5.6 (Braintree) @vendure-community/braintree-plugin@1.0.0
@vendure/payments-plugin@3.5.6 (Mollie) @vendure-community/mollie-plugin@1.0.0
@vendure/sentry-plugin@3.5.6 @vendure-community/sentry-plugin@1.0.0
@vendure/stellate-plugin@3.5.6 @vendure-community/stellate-plugin@1.0.0
@vendure/job-queue-plugin@3.5.6 (pub-sub) @vendure-community/pub-sub-plugin@1.0.0

For example, to migrate the Stripe plugin:

npm uninstall @vendure/payments-plugin
npm install @vendure-community/stripe-plugin@1.0.0

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 ref...

Read more