Mercur

v2.4.1: Index engine, security hardening & Products Inbox

Nov 4, 2026

Mercur v2.4.1 is a hardening and performance release. It has breaking changes, so please read What's Changed before you upgrade.

What's New

Index engine

GET /admin/products, GET /vendor/products, GET /store/products, and GET /store/offers go through shared listProducts / listOffers helpers.

Filters that need another module (the vendor allowlist, sales channels, seller status, and scheduled closures) are answered by query.index. Everything else stays on a single query.graph with an exact count.

Admin and vendor lists return an exact count on the index path too. All product lists are cached (store 300s, admin and vendor 60s) with explicit invalidation tags.

Security

A full audit of @mercurjs/core enhanced security.

  • Store fields= traversal can no longer reach invite tokens, member emails, or bank and tax details.
  • Invite tokens are hashed and single-purpose.
  • Vendor routes have ownership checks across sales channels, collections, promotions, shipping options, price lists, returns, claims, exchanges, and reservations.
  • Vendor order edits ignore client-supplied prices.
  • A second offer for a variant already in the cart is rejected.
  • Payouts are capped at the captured net amount and add back every credit line except store credit.

Products Inbox

Operators get one admin table of everything stores are waiting on: newly proposed products and pending edit or deletion requests. It has search, Type/Store/date filters, sorting, and pagination, and it is served by the new GET /admin/product-changes and gated by the product_request flag.

On the vendor side, the product list and product page show the operator's "Changes requested" message until the store resubmits. Edits to draft and proposed products apply immediately instead of waiting for a separate approval, and the product list opens newest-first.

Feedback button

Both panels show the Mercur version in the top bar next to a Feedback button that sends a message to the Mercur team.

What's Changed

Admin & Vendor Panel

  • Removed status and status_reason from POST /admin/sellers/:id (breaking) – use the lifecycle endpoints. is_premium now needs sellers.premium:edit.
  • Removed payment and professional details from GET /vendor/sellers/me (breaking) – read them from GET /vendor/sellers/:id behind store:view.
  • Removed data from the admin payout list (breaking), and stored source_payment now keeps only id and latest_charge.
  • Dropped onboarding.data from the admin seller and payout-account defaults (breaking).
  • Made GET /vendor/feature-flags return an allowlist (breaking).
  • Made the invite token return only from create and resend (breaking).
  • Removed address_id from the vendor stock-location validators (breaking).
  • Removed Vendor API access for suspended and terminated sellers (breaking), except GET /vendor/sellers/me.
  • Restricted vendor uploads to images only (breaking).

Core

  • Bumped Medusa to 2.22.3 (breaking) – BigNumber / MathBN now throw on invalid input instead of returning NaN, and a malformed bodyParser.sizeLimit throws when the route is hit. The order cross-module count fix was upstreamed, so that patch is gone, and the four core-flows patches apply unchanged.
  • Turned caching on by default (breaking) – withMercur now enables the caching flag and registers the Medusa Caching Module. Opt out with featureFlags.caching: false.
  • Made the cache use Redis when projectConfig.redisUrl is set, and an in-memory store otherwise, so multi-instance deployments should set redisUrl.
  • Made @medusajs/index and @medusajs/caching-redis direct dependencies of the API app and the basic template.
  • Hashed member-invite tokens in place (member_invite.token_hash) and created the writable offer_seller and offer_product link tables, with a backfill for existing offers – run medusa db:migrate before upgrading.
  • Made a one-time index sync necessary (breaking) – run POST /admin/index/sync so existing sellers and offers are ingested. Sellers created before this release never reached the index, because their events were dropped.

What's Fixed

Admin & Vendor Panel

  • Made the dashboard SDK virtual modules import only the files they read, fixing the blank panel in Vite dev when a route imports the pages entry.

Core

  • Kept split-order cart completion consistent across crashes: the order_cart link marks a completed cart, a leftover order group is reused, and a failed completion cancels or refunds the payment.
  • Refunded a captured payment when split-order cart completion fails after a captured webhook.
  • Kept the index engine Product sync off the review and pricing graphs, fixing the "Cannot read properties of undefined (reading 'id')" sync error on boot.
  • Emitted seller created and updated events again, so cached seller reads invalidate and subscribers run.
  • Named the product-delete when block in apply-product-change-actions.

Other

  • Taught create-mercur-app to check PostgreSQL and create the database before scaffolding, and to explain how to fix a server, credential, or database-name problem.
  • Sent each telemetry milestone exactly once.
  • Bumped sanitize-html to 2.18.1, and zod, i18next-http-backend, and vitest to patched versions.
  • Fixed broken links and references to the deleted new branch in the docs.
  • Fixed typos in the README contribution section.

More product updates

Ready to build?

Read the release notes, then see the product – open the demo, or book a walkthrough on your terms.