v2.4.1: Index engine, security hardening & Products Inbox
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
statusandstatus_reasonfromPOST /admin/sellers/:id(breaking) – use the lifecycle endpoints.is_premiumnow needssellers.premium:edit. - Removed payment and professional details from
GET /vendor/sellers/me(breaking) – read them fromGET /vendor/sellers/:idbehindstore:view. - Removed
datafrom the admin payout list (breaking), and storedsource_paymentnow keeps onlyidandlatest_charge. - Dropped
onboarding.datafrom the admin seller and payout-account defaults (breaking). - Made
GET /vendor/feature-flagsreturn an allowlist (breaking). - Made the invite token return only from create and resend (breaking).
- Removed
address_idfrom 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/MathBNnow throw on invalid input instead of returningNaN, and a malformedbodyParser.sizeLimitthrows 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) –
withMercurnow enables the caching flag and registers the Medusa Caching Module. Opt out withfeatureFlags.caching: false. - Made the cache use Redis when
projectConfig.redisUrlis set, and an in-memory store otherwise, so multi-instance deployments should setredisUrl. - Made
@medusajs/indexand@medusajs/caching-redisdirect dependencies of the API app and the basic template. - Hashed member-invite tokens in place (
member_invite.token_hash) and created the writableoffer_sellerandoffer_productlink tables, with a backfill for existing offers – runmedusa db:migratebefore upgrading. - Made a one-time index sync necessary (breaking) – run
POST /admin/index/syncso 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_cartlink 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
whenblock inapply-product-change-actions.
Other
- Taught
create-mercur-appto 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-htmlto 2.18.1, andzod,i18next-http-backend, andvitestto patched versions. - Fixed broken links and references to the deleted
newbranch 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.


